diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/fbtest.cpp | 61 | ||||
| -rw-r--r-- | utils/kmsblank.cpp | 103 | ||||
| -rw-r--r-- | utils/kmscapture.cpp | 419 | ||||
| -rw-r--r-- | utils/kmsprint.cpp | 525 | ||||
| -rw-r--r-- | utils/kmstest.cpp | 1210 | ||||
| -rw-r--r-- | utils/kmstouch.cpp | 293 | ||||
| -rw-r--r-- | utils/kmsview.cpp | 121 | ||||
| -rw-r--r-- | utils/meson.build | 26 |
8 files changed, 2758 insertions, 0 deletions
diff --git a/utils/fbtest.cpp b/utils/fbtest.cpp new file mode 100644 index 0000000..4992895 --- /dev/null +++ b/utils/fbtest.cpp @@ -0,0 +1,61 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include <linux/fb.h> + +#include <kms++util/kms++util.h> + +using namespace kms; + +int main(int argc, char** argv) +{ + const char* fbdev = "/dev/fb0"; + int r; + + int fd = open(fbdev, O_RDWR); + FAIL_IF(fd < 0, "open %s failed\n", fbdev); + + struct fb_var_screeninfo var; + + r = ioctl(fd, FBIOGET_VSCREENINFO, &var); + FAIL_IF(r, "FBIOGET_VSCREENINFO failed"); + + struct fb_fix_screeninfo fix; + + r = ioctl(fd, FBIOGET_FSCREENINFO, &fix); + FAIL_IF(r, "FBIOGET_FSCREENINFO failed"); + + uint8_t* ptr = static_cast<uint8_t*>(mmap(NULL, + var.yres_virtual * fix.line_length, + PROT_WRITE | PROT_READ, + MAP_SHARED, fd, 0)); + + FAIL_IF(ptr == MAP_FAILED, "mmap failed"); + + PixelFormat fmt = var.bits_per_pixel == 16 ? PixelFormat::RGB565 : PixelFormat::XRGB8888; + + ExtCPUFramebuffer buf(var.xres, var.yres, fmt, + ptr, var.yres_virtual * fix.line_length, fix.line_length, 0); + + printf("%s: res %dx%d, virtual %dx%d, line_len %d\n", + fbdev, + var.xres, var.yres, + var.xres_virtual, var.yres_virtual, + fix.line_length); + + draw_test_pattern(buf); + // XXX this may draw over the edge for narrow displays + draw_text(buf, buf.width() / 2, 0, fbdev, RGB(255, 255, 255)); + + close(fd); + + return 0; +} diff --git a/utils/kmsblank.cpp b/utils/kmsblank.cpp new file mode 100644 index 0000000..43bd2a2 --- /dev/null +++ b/utils/kmsblank.cpp @@ -0,0 +1,103 @@ +#include <stdio.h> +#include <unistd.h> +#include <algorithm> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +using namespace std; +using namespace kms; + +static const char* usage_str = + "Usage: kmsblank [OPTION]...\n\n" + "Blank screen(s)\n\n" + "Options:\n" + " --device=DEVICE DEVICE is the path to DRM card to open\n" + " -C, --card=NUM open /dev/dri/card<NUM>\n" + " -c, --connector=CONN CONN is <connector>\n" + " -t, --time=TIME blank/unblank in TIME intervals\n" + "\n" + "<connector> can be given by index (<idx>) or id (@<id>).\n" + "<connector> can also be given by name.\n"; + +static void usage() +{ + puts(usage_str); +} + +int main(int argc, char** argv) +{ + string dev_path; + + vector<string> conn_strs; + uint32_t time = 0; + + OptionSet optionset = { + Option("|device=", [&dev_path](string s) { + dev_path = s; + }), + Option("C|card=", [&dev_path](string s) { + dev_path = "/dev/dri/card" + s; + }), + Option("c|connector=", [&conn_strs](string str) { + conn_strs.push_back(str); + }), + Option("t|time=", [&time](string str) { + time = stoul(str); + }), + Option("h|help", []() { + usage(); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + usage(); + exit(-1); + } + + Card card(dev_path); + ResourceManager resman(card); + + vector<Connector*> conns; + + if (conn_strs.size() > 0) { + for (string s : conn_strs) { + auto c = resman.reserve_connector(s); + if (!c) + EXIT("Failed to resolve connector '%s'", s.c_str()); + conns.push_back(c); + } + } else { + conns = card.get_connectors(); + } + + bool blank = true; + + while (true) { + for (Connector* conn : conns) { + if (!conn->connected()) { + printf("Connector %u not connected\n", conn->idx()); + continue; + } + + printf("Connector %u: %sblank\n", conn->idx(), blank ? "" : "un"); + int r = conn->set_prop_value("DPMS", blank ? 3 : 0); + if (r) + EXIT("Failed to set DPMS: %d", r); + } + + if (time == 0) + break; + + usleep(1000 * time); + + blank = !blank; + } + + printf("press enter to exit\n"); + + getchar(); +} diff --git a/utils/kmscapture.cpp b/utils/kmscapture.cpp new file mode 100644 index 0000000..251cc3b --- /dev/null +++ b/utils/kmscapture.cpp @@ -0,0 +1,419 @@ +#include <linux/videodev2.h> +#include <cstdio> +#include <string.h> +#include <poll.h> +#include <fcntl.h> +#include <unistd.h> +#include <fstream> +#include <sys/ioctl.h> +#include <glob.h> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +#define CAMERA_BUF_QUEUE_SIZE 3 +#define MAX_CAMERA 9 + +using namespace std; +using namespace kms; + +enum class BufferProvider { + DRM, + V4L2, +}; + +class CameraPipeline +{ +public: + CameraPipeline(int cam_fd, Card& card, Crtc* crtc, Plane* plane, uint32_t x, uint32_t y, + uint32_t iw, uint32_t ih, PixelFormat pixfmt, + BufferProvider buffer_provider); + ~CameraPipeline(); + + CameraPipeline(const CameraPipeline& other) = delete; + CameraPipeline& operator=(const CameraPipeline& other) = delete; + + void show_next_frame(AtomicReq& req); + int fd() const { return m_fd; } + void start_streaming(); + +private: + DmabufFramebuffer* GetDmabufFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt); + int m_fd; /* camera file descriptor */ + Crtc* m_crtc; + Plane* m_plane; + BufferProvider m_buffer_provider; + vector<Framebuffer*> m_fb; + int m_prev_fb_index; + uint32_t m_in_width, m_in_height; /* camera capture resolution */ + /* image properties for display */ + uint32_t m_out_width, m_out_height; + uint32_t m_out_x, m_out_y; +}; + +static int buffer_export(int v4lfd, enum v4l2_buf_type bt, uint32_t index, int* dmafd) +{ + struct v4l2_exportbuffer expbuf; + + memset(&expbuf, 0, sizeof(expbuf)); + expbuf.type = bt; + expbuf.index = index; + if (ioctl(v4lfd, VIDIOC_EXPBUF, &expbuf) == -1) { + perror("VIDIOC_EXPBUF"); + return -1; + } + + *dmafd = expbuf.fd; + + return 0; +} + +DmabufFramebuffer* CameraPipeline::GetDmabufFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt) +{ + int r, dmafd; + + r = buffer_export(m_fd, V4L2_BUF_TYPE_VIDEO_CAPTURE, i, &dmafd); + ASSERT(r == 0); + + const PixelFormatInfo& format_info = get_pixel_format_info(pixfmt); + ASSERT(format_info.num_planes == 1); + + vector<int> fds{ dmafd }; + vector<uint32_t> pitches{ format_info.stride(m_in_width, 0) }; + vector<uint32_t> offsets{ 0 }; + + return new DmabufFramebuffer(card, m_in_width, m_in_height, pixfmt, + fds, pitches, offsets); +} + +bool inline better_size(struct v4l2_frmsize_discrete* v4ldisc, + uint32_t iw, uint32_t ih, + uint32_t best_w, uint32_t best_h) +{ + if (v4ldisc->width <= iw && v4ldisc->height <= ih && + (v4ldisc->width >= best_w || v4ldisc->height >= best_h)) + return true; + + return false; +} + +CameraPipeline::CameraPipeline(int cam_fd, Card& card, Crtc* crtc, Plane* plane, uint32_t x, uint32_t y, + uint32_t iw, uint32_t ih, PixelFormat pixfmt, + BufferProvider buffer_provider) + : m_fd(cam_fd), m_crtc(crtc), m_buffer_provider(buffer_provider), m_prev_fb_index(-1) +{ + int r; + uint32_t best_w = 320; + uint32_t best_h = 240; + + struct v4l2_frmsizeenum v4lfrms = {}; + v4lfrms.pixel_format = pixel_format_to_fourcc(pixfmt); + while (ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms) == 0) { + if (v4lfrms.type != V4L2_FRMSIZE_TYPE_DISCRETE) { + v4lfrms.index++; + continue; + } + + if (v4lfrms.discrete.width > iw || v4lfrms.discrete.height > ih) { + //skip + } else if (v4lfrms.discrete.width == iw && v4lfrms.discrete.height == ih) { + // Exact match + best_w = v4lfrms.discrete.width; + best_h = v4lfrms.discrete.height; + break; + } else if (v4lfrms.discrete.width >= best_w || v4lfrms.discrete.height >= ih) { + best_w = v4lfrms.discrete.width; + best_h = v4lfrms.discrete.height; + } + + v4lfrms.index++; + }; + + m_out_width = m_in_width = best_w; + m_out_height = m_in_height = best_h; + /* Move it to the middle of the requested area */ + m_out_x = x + iw / 2 - m_out_width / 2; + m_out_y = y + ih / 2 - m_out_height / 2; + + printf("Capture: %ux%u\n", best_w, best_h); + + struct v4l2_format v4lfmt = {}; + v4lfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + r = ioctl(m_fd, VIDIOC_G_FMT, &v4lfmt); + ASSERT(r == 0); + + v4lfmt.fmt.pix.pixelformat = pixel_format_to_fourcc(pixfmt); + v4lfmt.fmt.pix.width = m_in_width; + v4lfmt.fmt.pix.height = m_in_height; + + r = ioctl(m_fd, VIDIOC_S_FMT, &v4lfmt); + ASSERT(r == 0); + + uint32_t v4l_mem; + + if (m_buffer_provider == BufferProvider::V4L2) + v4l_mem = V4L2_MEMORY_MMAP; + else + v4l_mem = V4L2_MEMORY_DMABUF; + + struct v4l2_requestbuffers v4lreqbuf = {}; + v4lreqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4lreqbuf.memory = v4l_mem; + v4lreqbuf.count = CAMERA_BUF_QUEUE_SIZE; + r = ioctl(m_fd, VIDIOC_REQBUFS, &v4lreqbuf); + ASSERT(r == 0); + ASSERT(v4lreqbuf.count == CAMERA_BUF_QUEUE_SIZE); + + struct v4l2_buffer v4lbuf = {}; + v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4lbuf.memory = v4l_mem; + + for (unsigned i = 0; i < CAMERA_BUF_QUEUE_SIZE; i++) { + Framebuffer* fb; + + if (m_buffer_provider == BufferProvider::V4L2) + fb = GetDmabufFrameBuffer(card, i, pixfmt); + else + fb = new DumbFramebuffer(card, m_in_width, + m_in_height, pixfmt); + + v4lbuf.index = i; + if (m_buffer_provider == BufferProvider::DRM) + v4lbuf.m.fd = fb->prime_fd(0); + r = ioctl(m_fd, VIDIOC_QBUF, &v4lbuf); + ASSERT(r == 0); + + m_fb.push_back(fb); + } + + m_plane = plane; + + // Do initial plane setup with first fb, so that we only need to + // set the FB when page flipping + AtomicReq req(card); + + Framebuffer* fb = m_fb[0]; + + req.add(m_plane, "CRTC_ID", m_crtc->id()); + req.add(m_plane, "FB_ID", fb->id()); + + req.add(m_plane, "CRTC_X", m_out_x); + req.add(m_plane, "CRTC_Y", m_out_y); + req.add(m_plane, "CRTC_W", m_out_width); + req.add(m_plane, "CRTC_H", m_out_height); + + req.add(m_plane, "SRC_X", 0); + req.add(m_plane, "SRC_Y", 0); + req.add(m_plane, "SRC_W", m_in_width << 16); + req.add(m_plane, "SRC_H", m_in_height << 16); + + r = req.commit_sync(); + FAIL_IF(r, "initial plane setup failed"); +} + +CameraPipeline::~CameraPipeline() +{ + for (unsigned i = 0; i < m_fb.size(); i++) + delete m_fb[i]; + + ::close(m_fd); +} + +void CameraPipeline::start_streaming() +{ + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + int r = ioctl(m_fd, VIDIOC_STREAMON, &type); + FAIL_IF(r, "Failed to enable camera stream: %d", r); +} + +void CameraPipeline::show_next_frame(AtomicReq& req) +{ + int r; + uint32_t v4l_mem; + + if (m_buffer_provider == BufferProvider::V4L2) + v4l_mem = V4L2_MEMORY_MMAP; + else + v4l_mem = V4L2_MEMORY_DMABUF; + + struct v4l2_buffer v4l2buf = {}; + v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buf.memory = v4l_mem; + r = ioctl(m_fd, VIDIOC_DQBUF, &v4l2buf); + if (r != 0) { + printf("VIDIOC_DQBUF ioctl failed with %d\n", errno); + return; + } + + unsigned fb_index = v4l2buf.index; + + Framebuffer* fb = m_fb[fb_index]; + + req.add(m_plane, "FB_ID", fb->id()); + + if (m_prev_fb_index >= 0) { + memset(&v4l2buf, 0, sizeof(v4l2buf)); + v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buf.memory = v4l_mem; + v4l2buf.index = m_prev_fb_index; + if (m_buffer_provider == BufferProvider::DRM) + v4l2buf.m.fd = m_fb[m_prev_fb_index]->prime_fd(0); + r = ioctl(m_fd, VIDIOC_QBUF, &v4l2buf); + ASSERT(r == 0); + } + + m_prev_fb_index = fb_index; +} + +static bool is_capture_dev(int fd) +{ + struct v4l2_capability cap = {}; + int r = ioctl(fd, VIDIOC_QUERYCAP, &cap); + ASSERT(r == 0); + return cap.capabilities & V4L2_CAP_VIDEO_CAPTURE; +} + +std::vector<std::string> glob(const std::string& pat) +{ + glob_t glob_result; + glob(pat.c_str(), 0, NULL, &glob_result); + vector<string> ret; + for (unsigned i = 0; i < glob_result.gl_pathc; ++i) + ret.push_back(string(glob_result.gl_pathv[i])); + globfree(&glob_result); + return ret; +} + +static const char* usage_str = + "Usage: kmscapture [OPTIONS]\n\n" + "Options:\n" + " -s, --single Single camera mode. Open only /dev/video0\n" + " --buffer-type=<drm|v4l> Use DRM or V4L provided buffers. Default: DRM\n" + " -h, --help Print this help\n"; + +int main(int argc, char** argv) +{ + BufferProvider buffer_provider = BufferProvider::DRM; + bool single_cam = false; + + OptionSet optionset = { + Option("s|single", [&]() { + single_cam = true; + }), + Option("|buffer-type=", [&](string s) { + if (s == "v4l") + buffer_provider = BufferProvider::V4L2; + else if (s == "drm") + buffer_provider = BufferProvider::DRM; + else + FAIL("Invalid buffer provider: %s", s.c_str()); + }), + Option("h|help", [&]() { + puts(usage_str); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + puts(usage_str); + exit(-1); + } + + auto pixfmt = PixelFormat::YUYV; + + Card card; + + auto conn = card.get_first_connected_connector(); + auto crtc = conn->get_current_crtc(); + printf("Display: %dx%d\n", crtc->width(), crtc->height()); + printf("Buffer provider: %s\n", buffer_provider == BufferProvider::V4L2 ? "V4L" : "DRM"); + + vector<int> camera_fds; + + for (const string& vidpath : glob("/dev/video*")) { + int fd = ::open(vidpath.c_str(), O_RDWR | O_NONBLOCK); + + if (fd < 0) + continue; + + if (!is_capture_dev(fd)) { + close(fd); + continue; + } + + camera_fds.push_back(fd); + printf("Using %s\n", vidpath.c_str()); + + if (single_cam) + break; + } + + FAIL_IF(camera_fds.size() == 0, "No cameras found"); + + vector<Plane*> available_planes; + for (Plane* p : crtc->get_possible_planes()) { + if (p->plane_type() != PlaneType::Overlay) + continue; + + if (!p->supports_format(pixfmt)) + continue; + + available_planes.push_back(p); + } + + FAIL_IF(available_planes.size() < camera_fds.size(), "Not enough video planes for cameras"); + + uint32_t plane_w = crtc->width() / camera_fds.size(); + vector<CameraPipeline*> cameras; + + for (unsigned i = 0; i < camera_fds.size(); ++i) { + int cam_fd = camera_fds[i]; + Plane* plane = available_planes[i]; + + auto cam = new CameraPipeline(cam_fd, card, crtc, plane, i * plane_w, 0, + plane_w, crtc->height(), pixfmt, buffer_provider); + cameras.push_back(cam); + } + + unsigned nr_cameras = cameras.size(); + + vector<pollfd> fds(nr_cameras + 1); + + for (unsigned i = 0; i < nr_cameras; i++) { + fds[i].fd = cameras[i]->fd(); + fds[i].events = POLLIN; + } + fds[nr_cameras].fd = 0; + fds[nr_cameras].events = POLLIN; + + for (auto cam : cameras) + cam->start_streaming(); + + while (true) { + int r = poll(fds.data(), nr_cameras + 1, -1); + ASSERT(r > 0); + + if (fds[nr_cameras].revents != 0) + break; + + AtomicReq req(card); + + for (unsigned i = 0; i < nr_cameras; i++) { + if (!fds[i].revents) + continue; + cameras[i]->show_next_frame(req); + fds[i].revents = 0; + } + + r = req.test(); + FAIL_IF(r, "Atomic commit failed: %d", r); + + req.commit_sync(); + } + + for (auto cam : cameras) + delete cam; +} diff --git a/utils/kmsprint.cpp b/utils/kmsprint.cpp new file mode 100644 index 0000000..849c05c --- /dev/null +++ b/utils/kmsprint.cpp @@ -0,0 +1,525 @@ +#include <algorithm> +#include <cinttypes> +#include <cstdio> +#include <iostream> +#include <string> +#include <unistd.h> +#include <kms++/format.h> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +using namespace std; +using namespace kms; + +static struct { + bool print_props; + bool print_modes; + bool print_list; + bool x_modeline; +} s_opts; + +static string format_mode(const Videomode& m, unsigned idx) +{ + string str; + + str = fmt::format(" {:2} ", idx); + + if (s_opts.x_modeline) { + str += fmt::format("{:12} {:6} {:4} {:4} {:4} {:4} {:4} {:4} {:4} {:4} {:3} {:#x} {:#x}", + m.name, + m.clock, + m.hdisplay, m.hsync_start, m.hsync_end, m.htotal, + m.vdisplay, m.vsync_start, m.vsync_end, m.vtotal, + m.vrefresh, + m.flags, + m.type); + } else { + str += m.to_string_long_padded(); + } + + return str; +} + +static string format_mode_short(const Videomode& m) +{ + return m.to_string_long(); +} + +static string format_connector(Connector& c) +{ + string str; + + str = fmt::format("Connector {} ({}) {}", + c.idx(), c.id(), c.fullname()); + + switch (c.connector_status()) { + case ConnectorStatus::Connected: + str += " (connected)"; + break; + case ConnectorStatus::Disconnected: + str += " (disconnected)"; + break; + default: + str += " (unknown)"; + break; + } + + return str; +} + +static string format_encoder(Encoder& e) +{ + return fmt::format("Encoder {} ({}) {}", + e.idx(), e.id(), e.get_encoder_type()); +} + +static string format_crtc(Crtc& c) +{ + string str; + + str = fmt::format("Crtc {} ({})", c.idx(), c.id()); + + if (c.mode_valid()) + str += " " + format_mode_short(c.mode()); + + return str; +} + +static string format_plane(Plane& p) +{ + string str; + + str = fmt::format("Plane {} ({})", p.idx(), p.id()); + + if (p.fb_id()) + str += fmt::format(" fb-id: {}", p.fb_id()); + + string crtcs = join<Crtc*>(p.get_possible_crtcs(), " ", [](Crtc* crtc) { return to_string(crtc->idx()); }); + + str += fmt::format(" (crtcs: {})", crtcs); + + if (p.card().has_atomic()) { + str += fmt::format(" {},{} {}x{} -> {},{} {}x{}", + (uint32_t)p.get_prop_value("SRC_X") >> 16, + (uint32_t)p.get_prop_value("SRC_Y") >> 16, + (uint32_t)p.get_prop_value("SRC_W") >> 16, + (uint32_t)p.get_prop_value("SRC_H") >> 16, + (int32_t)p.get_prop_value("CRTC_X"), + (int32_t)p.get_prop_value("CRTC_Y"), + (uint32_t)p.get_prop_value("CRTC_W"), + (uint32_t)p.get_prop_value("CRTC_H")); + } + + string fmts = join<uint32_t>(p.get_fourccs(), " ", [](uint32_t fourcc) { return fourcc_to_str(fourcc); }); + + str += fmt::format(" ({})", fmts); + + return str; +} + +static string format_fb(Framebuffer& fb) +{ + return fmt::format("FB {} {}x{} {}", + fb.id(), fb.width(), fb.height(), + fourcc_to_str(fb.fourcc())); +} + +static string format_property(const Property* prop, uint64_t val) +{ + string ret = fmt::format("{} ({}) = ", prop->name(), prop->id()); + + switch (prop->type()) { + case PropertyType::Bitmask: { + vector<string> v, vall; + + for (auto kvp : prop->get_enums()) { + if (val & (1 << kvp.first)) + v.push_back(kvp.second); + vall.push_back(fmt::format("{}={:#x}", kvp.second, 1 << kvp.first)); + } + + // XXX + ret += fmt::format("{:#x} ({}) [{}]", val, join(v, "|"), join(vall, "|")); + + break; + } + + case PropertyType::Blob: { + uint32_t blob_id = (uint32_t)val; + + if (blob_id) { + Blob blob(prop->card(), blob_id); + auto data = blob.data(); + + ret += fmt::format("blob-id {} len {}", blob_id, data.size()); + } else { + ret += fmt::format("blob-id {}", blob_id); + } + + break; + } + + case PropertyType::Enum: { + string cur; + vector<string> vall; + + for (auto kvp : prop->get_enums()) { + if (val == kvp.first) + cur = kvp.second; + vall.push_back(fmt::format("{}={}", kvp.second, kvp.first)); + } + + ret += fmt::format("{} ({}) [{}]", val, cur, join(vall, "|")); + + break; + } + + case PropertyType::Object: { + ret += fmt::format("object id {}", val); + break; + } + + case PropertyType::Range: { + auto values = prop->get_values(); + + ret += fmt::format("{} [{} - {}]", + val, values[0], values[1]); + + break; + } + + case PropertyType::SignedRange: { + auto values = prop->get_values(); + + ret += fmt::format("{} [{} - {}]", + (int64_t)val, (int64_t)values[0], (int64_t)values[1]); + + break; + } + } + + if (prop->is_pending()) + ret += " (pending)"; + if (prop->is_immutable()) + ret += " (immutable)"; + + return ret; +} + +static vector<string> format_props(DrmPropObject* o) +{ + vector<string> lines; + + auto pmap = o->get_prop_map(); + for (auto pp : pmap) { + const Property* p = o->card().get_prop(pp.first); + lines.push_back(format_property(p, pp.second)); + } + + return lines; +} + +static string format_ob(DrmObject* ob) +{ + if (auto o = dynamic_cast<Connector*>(ob)) + return format_connector(*o); + else if (auto o = dynamic_cast<Encoder*>(ob)) + return format_encoder(*o); + else if (auto o = dynamic_cast<Crtc*>(ob)) + return format_crtc(*o); + else if (auto o = dynamic_cast<Plane*>(ob)) + return format_plane(*o); + else if (auto o = dynamic_cast<Framebuffer*>(ob)) + return format_fb(*o); + else + EXIT("Unkown DRM Object type\n"); +} + +template<class T> +vector<T> filter(const vector<T>& sequence, function<bool(T)> predicate) +{ + vector<T> result; + + for (auto it = sequence.begin(); it != sequence.end(); ++it) + if (predicate(*it)) + result.push_back(*it); + + return result; +} + +struct Entry { + string title; + vector<string> lines; + vector<Entry> children; +}; + +static Entry& add_entry(vector<Entry>& entries) +{ + entries.emplace_back(); + return entries.back(); +} +/* +static bool on_tty() +{ + return isatty(STDOUT_FILENO) > 0; +} +*/ +enum class TreeGlyphMode { + None, + ASCII, + UTF8, +}; + +static TreeGlyphMode s_glyph_mode = TreeGlyphMode::None; + +enum class TreeGlyph { + Vertical, + Branch, + Right, + Space, +}; + +static const map<TreeGlyph, string> glyphs_utf8 = { + { TreeGlyph::Vertical, "│ " }, + { TreeGlyph::Branch, "├─" }, + { TreeGlyph::Right, "└─" }, + { TreeGlyph::Space, " " }, + +}; + +static const map<TreeGlyph, string> glyphs_ascii = { + { TreeGlyph::Vertical, "| " }, + { TreeGlyph::Branch, "|-" }, + { TreeGlyph::Right, "`-" }, + { TreeGlyph::Space, " " }, + +}; + +const string& get_glyph(TreeGlyph glyph) +{ + static const string s_empty = " "; + + if (s_glyph_mode == TreeGlyphMode::None) + return s_empty; + + const map<TreeGlyph, string>& glyphs = s_glyph_mode == TreeGlyphMode::UTF8 ? glyphs_utf8 : glyphs_ascii; + + return glyphs.at(glyph); +} + +static void print_entry(const Entry& e, const string& prefix, bool is_child, bool is_last) +{ + string prefix1; + string prefix2; + + if (is_child) { + prefix1 = prefix + (is_last ? get_glyph(TreeGlyph::Right) : get_glyph(TreeGlyph::Branch)); + prefix2 = prefix + (is_last ? get_glyph(TreeGlyph::Space) : get_glyph(TreeGlyph::Vertical)); + } + + fmt::print("{}{}\n", prefix1, e.title); + + bool has_children = e.children.size() > 0; + + string data_prefix = prefix2 + (has_children ? get_glyph(TreeGlyph::Vertical) : get_glyph(TreeGlyph::Space)); + + for (const string& str : e.lines) { + string p = data_prefix + get_glyph(TreeGlyph::Space); + fmt::print("{}{}\n", p, str); + } + + for (const Entry& child : e.children) { + bool child_is_last = &child == &e.children.back(); + + print_entry(child, prefix2, true, child_is_last); + } +} + +static void print_entries(const vector<Entry>& entries, const string& prefix) +{ + for (const Entry& e : entries) { + print_entry(e, "", false, false); + } +} + +template<class T> +static void append(vector<DrmObject*>& dst, const vector<T*>& src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +static void print_as_list(Card& card) +{ + vector<DrmPropObject*> obs; + vector<Framebuffer*> fbs; + + for (Connector* conn : card.get_connectors()) { + obs.push_back(conn); + } + + for (Encoder* enc : card.get_encoders()) { + obs.push_back(enc); + } + + for (Crtc* crtc : card.get_crtcs()) { + obs.push_back(crtc); + if (crtc->buffer_id() && !card.has_universal_planes()) { + Framebuffer* fb = new Framebuffer(card, crtc->buffer_id()); + fbs.push_back(fb); + } + } + + for (Plane* plane : card.get_planes()) { + obs.push_back(plane); + if (plane->fb_id()) { + Framebuffer* fb = new Framebuffer(card, plane->fb_id()); + fbs.push_back(fb); + } + } + + for (DrmPropObject* ob : obs) { + fmt::print("{}\n", format_ob(ob)); + + if (s_opts.print_props) { + for (string str : format_props(ob)) + fmt::print(" {}\n", str); + } + } + + for (Framebuffer* fb : fbs) { + fmt::print("{}\n", format_ob(fb)); + } +} + +static void print_as_tree(Card& card) +{ + vector<Entry> entries; + + for (Connector* conn : card.get_connectors()) { + Entry& e1 = add_entry(entries); + e1.title = format_ob(conn); + if (s_opts.print_props) + e1.lines = format_props(conn); + + for (Encoder* enc : conn->get_encoders()) { + Entry& e2 = add_entry(e1.children); + e2.title = format_ob(enc); + if (s_opts.print_props) + e2.lines = format_props(enc); + + if (Crtc* crtc = enc->get_crtc()) { + Entry& e3 = add_entry(e2.children); + e3.title = format_ob(crtc); + if (s_opts.print_props) + e3.lines = format_props(crtc); + + if (crtc->buffer_id() && !card.has_universal_planes()) { + Framebuffer fb(card, crtc->buffer_id()); + Entry& e5 = add_entry(e3.children); + + e5.title = format_ob(&fb); + } + + for (Plane* plane : card.get_planes()) { + if (plane->crtc_id() != crtc->id()) + continue; + + Entry& e4 = add_entry(e3.children); + e4.title = format_ob(plane); + if (s_opts.print_props) + e4.lines = format_props(plane); + + uint32_t fb_id = plane->fb_id(); + if (fb_id) { + Framebuffer fb(card, fb_id); + + Entry& e5 = add_entry(e4.children); + + e5.title = format_ob(&fb); + } + } + } + } + } + + print_entries(entries, ""); +} + +static void print_modes(Card& card) +{ + for (Connector* conn : card.get_connectors()) { + if (!conn->connected()) + continue; + + fmt::print("{}\n", format_ob(conn)); + + auto modes = conn->get_modes(); + for (unsigned i = 0; i < modes.size(); ++i) + fmt::print("{}\n", format_mode(modes[i], i)); + } +} + +static const char* usage_str = + "Usage: kmsprint [OPTIONS]\n\n" + "Options:\n" + " --device=DEVICE DEVICE is the path to DRM card to open\n" + " -C, --card=NUM open /dev/dri/card<NUM>\n" + " -l, --list Print list instead of tree\n" + " -m, --modes Print modes\n" + " --xmode Print modes using X modeline\n" + " -p, --props Print properties\n"; + +static void usage() +{ + puts(usage_str); +} + +int main(int argc, char** argv) +{ + string dev_path; + + OptionSet optionset = { + Option("|device=", [&dev_path](string s) { + dev_path = s; + }), + Option("C|card=", [&dev_path](string s) { + dev_path = "/dev/dri/card" + s; + }), + Option("l|list", []() { + s_opts.print_list = true; + }), + Option("m|modes", []() { + s_opts.print_modes = true; + }), + Option("p|props", []() { + s_opts.print_props = true; + }), + Option("|xmode", []() { + s_opts.x_modeline = true; + }), + Option("h|help", []() { + usage(); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + usage(); + exit(-1); + } + + Card card(dev_path); + + if (s_opts.print_modes) { + print_modes(card); + return 0; + } + + if (s_opts.print_list) + print_as_list(card); + else + print_as_tree(card); +} diff --git a/utils/kmstest.cpp b/utils/kmstest.cpp new file mode 100644 index 0000000..cc21b92 --- /dev/null +++ b/utils/kmstest.cpp @@ -0,0 +1,1210 @@ +#include <cstdio> +#include <cstring> +#include <algorithm> +#include <regex> +#include <set> +#include <chrono> +#include <cstdint> +#include <cinttypes> +#include <thread> + +#include <sys/select.h> + +#include <kms++/format.h> + +#include <kms++/kms++.h> +#include <kms++/modedb.h> +#include <kms++/mode_cvt.h> + +#include <kms++util/kms++util.h> + +using namespace std; +using namespace kms; + +struct PropInfo { + PropInfo(string n, uint64_t v) + : prop(NULL), name(n), val(v) {} + + Property* prop; + string name; + uint64_t val; +}; + +struct PlaneInfo { + Plane* plane; + + signed x; + signed y; + unsigned w; + unsigned h; + + unsigned view_x; + unsigned view_y; + unsigned view_w; + unsigned view_h; + + vector<Framebuffer*> fbs; + + vector<PropInfo> props; +}; + +struct OutputInfo { + Connector* connector; + + Crtc* crtc; + Videomode mode; + vector<Framebuffer*> legacy_fbs; + + vector<PlaneInfo> planes; + + vector<PropInfo> conn_props; + vector<PropInfo> crtc_props; +}; + +static bool s_use_dmt; +static bool s_use_cea; +static unsigned s_num_buffers = 1; +static bool s_flip_mode; +static bool s_flip_sync; +static bool s_cvt; +static bool s_cvt_v2; +static bool s_cvt_vid_opt; +static unsigned s_max_flips; +static bool s_print_crc; +static unsigned s_run_time; +static TestPatternOptions s_pattern_options; + +__attribute__((unused)) static void print_regex_match(smatch sm) +{ + for (unsigned i = 0; i < sm.size(); ++i) { + string str = sm[i].str(); + fmt::print("{}: {}\n", i, str); + } +} + +static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "") +{ + Connector* conn = resman.reserve_connector(str); + + if (!conn) + EXIT("No connector '%s'", str.c_str()); + + output.connector = conn; + output.mode = output.connector->get_default_mode(); +} + +static void get_default_crtc(ResourceManager& resman, OutputInfo& output) +{ + output.crtc = resman.reserve_crtc(output.connector); + + if (!output.crtc) + EXIT("Could not find available crtc"); +} + +static PlaneInfo* add_default_planeinfo(OutputInfo* output) +{ + output->planes.push_back(PlaneInfo{}); + PlaneInfo* ret = &output->planes.back(); + ret->w = output->mode.hdisplay; + ret->h = output->mode.vdisplay; + return ret; +} + +static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output) +{ + // @12:1920x1200i@60 + // @12:33000000,800/210/30/16/-,480/22/13/10/-,i + + const regex modename_re("(?:(@?)(\\d+):)?" // @12: + "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i + "(?:@([\\d\\.]+))?"); // @60 + + const regex modeline_re("(?:(@?)(\\d+):)?" // @12: + "(\\d+)," // 33000000, + "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-\\?])," // 800/210/30/16/-, + "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-\\?])" // 480/22/13/10/- + "(?:,([i]+))?" // ,i + ); + + smatch sm; + if (regex_match(crtc_str, sm, modename_re)) { + if (sm[2].matched) { + bool use_id = sm[1].length() == 1; + unsigned num = stoul(sm[2].str()); + + if (use_id) { + Crtc* c = card.get_crtc(num); + if (!c) + EXIT("Bad crtc id '%u'", num); + + output.crtc = c; + } else { + auto crtcs = card.get_crtcs(); + + if (num >= crtcs.size()) + EXIT("Bad crtc number '%u'", num); + + output.crtc = crtcs[num]; + } + } else { + output.crtc = output.connector->get_current_crtc(); + } + + unsigned w = stoul(sm[3]); + unsigned h = stoul(sm[4]); + bool ilace = sm[5].matched ? true : false; + float refresh = sm[6].matched ? stof(sm[6]) : 0; + + if (s_cvt) { + output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt); + } else if (s_use_dmt) { + try { + output.mode = find_dmt(w, h, refresh, ilace); + } catch (exception& e) { + EXIT("Mode not found from DMT tables\n"); + } + } else if (s_use_cea) { + try { + output.mode = find_cea(w, h, refresh, ilace); + } catch (exception& e) { + EXIT("Mode not found from CEA tables\n"); + } + } else { + try { + output.mode = output.connector->get_mode(w, h, refresh, ilace); + } catch (exception& e) { + EXIT("Mode not found from the connector\n"); + } + } + } else if (regex_match(crtc_str, sm, modeline_re)) { + if (sm[2].matched) { + bool use_id = sm[1].length() == 1; + unsigned num = stoul(sm[2].str()); + + if (use_id) { + Crtc* c = card.get_crtc(num); + if (!c) + EXIT("Bad crtc id '%u'", num); + + output.crtc = c; + } else { + auto crtcs = card.get_crtcs(); + + if (num >= crtcs.size()) + EXIT("Bad crtc number '%u'", num); + + output.crtc = crtcs[num]; + } + } else { + output.crtc = output.connector->get_current_crtc(); + } + + unsigned clock = stoul(sm[3]); + + unsigned hact = stoul(sm[4]); + unsigned hfp = stoul(sm[5]); + unsigned hsw = stoul(sm[6]); + unsigned hbp = stoul(sm[7]); + + SyncPolarity h_sync; + switch (sm[8].str()[0]) { + case '+': h_sync = SyncPolarity::Positive; break; + case '-': h_sync = SyncPolarity::Negative; break; + default: h_sync = SyncPolarity::Undefined; break; + } + + unsigned vact = stoul(sm[9]); + unsigned vfp = stoul(sm[10]); + unsigned vsw = stoul(sm[11]); + unsigned vbp = stoul(sm[12]); + + SyncPolarity v_sync; + switch (sm[13].str()[0]) { + case '+': v_sync = SyncPolarity::Positive; break; + case '-': v_sync = SyncPolarity::Negative; break; + default: v_sync = SyncPolarity::Undefined; break; + } + + output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp); + output.mode.set_hsync(h_sync); + output.mode.set_vsync(v_sync); + + if (sm[14].matched) { + for (int i = 0; i < sm[14].length(); ++i) { + char f = string(sm[14])[i]; + + switch (f) { + case 'i': + output.mode.set_interlace(true); + break; + default: + EXIT("Bad mode flag %c", f); + } + } + } + } else { + EXIT("Failed to parse crtc option '%s'", crtc_str.c_str()); + } + + if (output.crtc) + output.crtc = resman.reserve_crtc(output.crtc); + else + output.crtc = resman.reserve_crtc(output.connector); + + if (!output.crtc) + EXIT("Could not find available crtc"); +} + +static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo) +{ + // 3:400,400-400x400 + const regex plane_re("(?:(@?)(\\d+):)?" // 3: + "(?:(-?\\d+),(-?\\d+)-)?" // 400,400- + "(\\d+)x(\\d+)"); // 400x400 + + smatch sm; + if (!regex_match(plane_str, sm, plane_re)) + EXIT("Failed to parse plane option '%s'", plane_str.c_str()); + + if (sm[2].matched) { + bool use_id = sm[1].length() == 1; + unsigned num = stoul(sm[2].str()); + + if (use_id) { + Plane* p = card.get_plane(num); + if (!p) + EXIT("Bad plane id '%u'", num); + + pinfo.plane = p; + } else { + auto planes = card.get_planes(); + + if (num >= planes.size()) + EXIT("Bad plane number '%u'", num); + + pinfo.plane = planes[num]; + } + + auto plane = resman.reserve_plane(pinfo.plane); + if (!plane) + EXIT("Plane id %u is not available", pinfo.plane->id()); + } + + pinfo.w = stoul(sm[5]); + pinfo.h = stoul(sm[6]); + + if (sm[3].matched) + pinfo.x = stol(sm[3]); + else + pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2; + + if (sm[4].matched) + pinfo.y = stol(sm[4]); + else + pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2; +} + +static void parse_prop(const string& prop_str, vector<PropInfo>& props) +{ + string name, val; + + size_t split = prop_str.find("="); + + if (split == string::npos) + EXIT("Equal sign ('=') not found in %s", prop_str.c_str()); + + name = prop_str.substr(0, split); + val = prop_str.substr(split + 1); + + props.push_back(PropInfo(name, stoull(val, 0, 0))); +} + +static void get_props(Card& card, vector<PropInfo>& props, const DrmPropObject* propobj) +{ + for (auto& pi : props) + pi.prop = propobj->get_prop(pi.name); +} + +static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height) +{ + vector<Framebuffer*> v; + + for (unsigned i = 0; i < s_num_buffers; ++i) + v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888)); + + return v; +} + +static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo) +{ + unsigned w, h; + PixelFormat format = PixelFormat::XRGB8888; + + if (pinfo) { + w = pinfo->w; + h = pinfo->h; + } else { + w = output->mode.hdisplay; + h = output->mode.vdisplay; + } + + if (!fb_str.empty()) { + // XXX the regexp is not quite correct + // 400x400-NV12 + const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400 + "(?:-)?" // - + "(\\w+)?"); // NV12 + + smatch sm; + if (!regex_match(fb_str, sm, fb_re)) + EXIT("Failed to parse fb option '%s'", fb_str.c_str()); + + if (sm[1].matched) + w = stoul(sm[1]); + if (sm[2].matched) + h = stoul(sm[2]); + if (sm[3].matched) { + try { + format = find_pixel_format_by_name(sm[3]); + } catch (const invalid_argument& e) { + format = fourcc_str_to_pixel_format(sm[3]); + } + } + } + + vector<Framebuffer*> v; + + for (unsigned i = 0; i < s_num_buffers; ++i) + v.push_back(new DumbFramebuffer(card, w, h, format)); + + if (pinfo) + pinfo->fbs = v; + else + output->legacy_fbs = v; +} + +static void parse_view(const string& view_str, PlaneInfo& pinfo) +{ + const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400 + + smatch sm; + if (!regex_match(view_str, sm, view_re)) + EXIT("Failed to parse view option '%s'", view_str.c_str()); + + pinfo.view_x = stoul(sm[1]); + pinfo.view_y = stoul(sm[2]); + pinfo.view_w = stoul(sm[3]); + pinfo.view_h = stoul(sm[4]); +} + +static const char* usage_str = + "Usage: kmstest [OPTION]...\n\n" + "Show a test pattern on a display or plane\n\n" + "Options:\n" + " --device=DEVICE DEVICE is the path to DRM card to open\n" + " -C, --card=NUM open /dev/dri/card<NUM>\n" + " -c, --connector=CONN CONN is <connector>\n" + " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n" + " or\n" + " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n" + " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n" + " -f, --fb=FB FB is [<w>x<h>][-][<fmtname>|<4cc>]\n" + " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n" + " -P, --property=PROP=VAL Set PROP to VAL in the previous DRM object\n" + " --dmt Search for the given mode from DMT tables\n" + " --cea Search for the given mode from CEA tables\n" + " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n" + " --flip[=max] Do page flipping for each output with an optional maximum flips count\n" + " --sync Synchronize page flipping\n" + " --crc Print CRC16 for framebuffer contents\n" + " -t, --time=SECONDS Run for SECONDS and exit (no stdin read)\n" + " -T, --pattern=PAT test, white, black, red, green, blue, smpte\n" + " --rec=REC bt601, bt709, bt2020\n" + " --range=RANGE limited, full\n" + "\n" + "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n" + "<connector> can also be given by name.\n" + "\n" + "Options can be given multiple times to set up multiple displays or planes.\n" + "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n" + "an earlier option.\n" + "If you omit parameters, kmstest tries to guess what you mean\n" + "\n" + "Examples:\n" + "\n" + "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n" + " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n" + "XR24 framebuffer on first connected connector in the default mode:\n" + " kmstest -f XR24\n\n" + "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n" + " kmstest -p 400x400 -f XR24\n\n" + "Test pattern on the second connector with default mode:\n" + " kmstest -c 1\n" + "\n" + "Environmental variables:\n" + " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n" + " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n"; + +static void usage() +{ + puts(usage_str); +} + +enum class ArgType { + Connector, + Crtc, + Plane, + Framebuffer, + View, + Property, +}; + +struct Arg { + ArgType type; + string arg; +}; + +static string s_device_path; + +static vector<Arg> parse_cmdline(int argc, char** argv) +{ + vector<Arg> args; + + OptionSet optionset = { + Option("|device=", + [&](string s) { + s_device_path = s; + }), + Option("C|card=", + [&](string s) { + s_device_path = "/dev/dri/card" + s; + }), + Option("c|connector=", + [&](string s) { + args.push_back(Arg{ ArgType::Connector, s }); + }), + Option("r|crtc=", [&](string s) { + args.push_back(Arg{ ArgType::Crtc, s }); + }), + Option("p|plane=", [&](string s) { + args.push_back(Arg{ ArgType::Plane, s }); + }), + Option("f|fb=", [&](string s) { + args.push_back(Arg{ ArgType::Framebuffer, s }); + }), + Option("v|view=", [&](string s) { + args.push_back(Arg{ ArgType::View, s }); + }), + Option("P|property=", [&](string s) { + args.push_back(Arg{ ArgType::Property, s }); + }), + Option("|dmt", []() { + s_use_dmt = true; + }), + Option("|cea", []() { + s_use_cea = true; + }), + Option("|flip?", [&](string s) { + s_flip_mode = true; + s_num_buffers = 2; + if (!s.empty()) + s_max_flips = stoi(s); + }), + Option("|sync", []() { + s_flip_sync = true; + }), + Option("|cvt=", [&](string s) { + if (s == "v1") + s_cvt = true; + else if (s == "v2") + s_cvt = s_cvt_v2 = true; + else if (s == "v2o") + s_cvt = s_cvt_v2 = s_cvt_vid_opt = true; + else { + usage(); + exit(-1); + } + }), + Option("|crc", []() { + s_print_crc = true; + }), + Option("t|time=", [&](string s) { + s_run_time = stoul(s); + }), + Option("T|pattern=", [&](string s) { + s_pattern_options.pattern = s; + }), + Option("|rec=", [&](string s) { + s = to_lower(s); + + if (s == "bt601") + s_pattern_options.rec = RecStandard::BT601; + else if (s == "bt709") + s_pattern_options.rec = RecStandard::BT709; + else if (s == "bt2020") + s_pattern_options.rec = RecStandard::BT2020; + else { + usage(); + exit(-1); + } + }), + Option("|range=", [&](string s) { + s = to_lower(s); + + if (s == "limited") + s_pattern_options.range = ColorRange::Limited; + else if (s == "full") + s_pattern_options.range = ColorRange::Full; + else { + usage(); + exit(-1); + } + }), + Option("h|help", [&]() { + usage(); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + usage(); + exit(-1); + } + + return args; +} + +static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args) +{ + vector<OutputInfo> outputs; + + OutputInfo* current_output = 0; + PlaneInfo* current_plane = 0; + + for (const auto& arg : output_args) { + switch (arg.type) { + case ArgType::Connector: { + outputs.push_back(OutputInfo{}); + current_output = &outputs.back(); + + get_connector(resman, *current_output, arg.arg); + current_plane = 0; + + break; + } + + case ArgType::Crtc: { + if (!current_output) { + outputs.push_back(OutputInfo{}); + current_output = &outputs.back(); + } + + if (!current_output->connector) + get_connector(resman, *current_output); + + parse_crtc(resman, card, arg.arg, *current_output); + + current_plane = 0; + + break; + } + + case ArgType::Plane: { + if (!current_output) { + outputs.push_back(OutputInfo{}); + current_output = &outputs.back(); + } + + if (!current_output->connector) + get_connector(resman, *current_output); + + if (!current_output->crtc) + get_default_crtc(resman, *current_output); + + current_plane = add_default_planeinfo(current_output); + + parse_plane(resman, card, arg.arg, *current_output, *current_plane); + + break; + } + + case ArgType::Framebuffer: { + if (!current_output) { + outputs.push_back(OutputInfo{}); + current_output = &outputs.back(); + } + + if (!current_output->connector) + get_connector(resman, *current_output); + + if (!current_output->crtc) + get_default_crtc(resman, *current_output); + + if (!current_plane && card.has_atomic()) + current_plane = add_default_planeinfo(current_output); + + parse_fb(card, arg.arg, current_output, current_plane); + + break; + } + + case ArgType::View: { + if (!current_plane || current_plane->fbs.empty()) + EXIT("'view' parameter requires a plane and a fb"); + + parse_view(arg.arg, *current_plane); + break; + } + + case ArgType::Property: { + if (!current_output) + EXIT("No object to which set the property"); + + if (current_plane) + parse_prop(arg.arg, current_plane->props); + else if (current_output->crtc) + parse_prop(arg.arg, current_output->crtc_props); + else if (current_output->connector) + parse_prop(arg.arg, current_output->conn_props); + else + EXIT("no object"); + + break; + } + } + } + + if (outputs.empty()) { + // no outputs defined, show a pattern on all connected screens + for (Connector* conn : card.get_connectors()) { + if (!conn->connected()) + continue; + + OutputInfo output = {}; + output.connector = resman.reserve_connector(conn); + EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str()); + output.crtc = resman.reserve_crtc(conn); + EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str()); + output.mode = output.connector->get_default_mode(); + + outputs.push_back(output); + } + } + + for (OutputInfo& o : outputs) { + get_props(card, o.conn_props, o.connector); + + if (!o.crtc) + get_default_crtc(resman, o); + + get_props(card, o.crtc_props, o.crtc); + + if (!o.mode.valid()) + EXIT("Mode not valid for %s", o.connector->fullname().c_str()); + + if (card.has_atomic()) { + if (o.planes.empty()) + add_default_planeinfo(&o); + } else { + if (o.legacy_fbs.empty()) + o.legacy_fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay); + } + + for (PlaneInfo& p : o.planes) { + if (p.fbs.empty()) + p.fbs = get_default_fb(card, p.w, p.h); + } + + for (PlaneInfo& p : o.planes) { + if (!p.plane) { + if (card.has_atomic()) + p.plane = resman.reserve_generic_plane(o.crtc, p.fbs[0]->format()); + else + p.plane = resman.reserve_overlay_plane(o.crtc, p.fbs[0]->format()); + + if (!p.plane) + EXIT("Failed to find available plane"); + } + get_props(card, p.props, p.plane); + } + } + + return outputs; +} + +static uint16_t crc16(uint16_t crc, uint8_t data) +{ + const uint16_t CRC16_IBM = 0x8005; + + for (uint8_t i = 0; i < 8; i++) { + if (((crc & 0x8000) >> 8) ^ (data & 0x80)) + crc = (crc << 1) ^ CRC16_IBM; + else + crc = (crc << 1); + + data <<= 1; + } + + return crc; +} + +static string fb_crc(IFramebuffer* fb) +{ + uint8_t* p = fb->map(0); + uint16_t r, g, b; + + r = g = b = 0; + + for (unsigned y = 0; y < fb->height(); ++y) { + for (unsigned x = 0; x < fb->width(); ++x) { + uint32_t* p32 = reinterpret_cast<uint32_t*>(p + fb->stride(0) * y + x * 4); + RGB rgb(*p32); + + r = crc16(r, rgb.r); + r = crc16(r, 0); + + g = crc16(g, rgb.g); + g = crc16(g, 0); + + b = crc16(b, rgb.b); + b = crc16(b, 0); + } + } + + return fmt::format("{:#06x} {:#06x} {:#06x}", r, g, b); +} + +static void print_outputs(const vector<OutputInfo>& outputs) +{ + for (unsigned i = 0; i < outputs.size(); ++i) { + const OutputInfo& o = outputs[i]; + + fmt::print("Connector {}/@{}: {}", o.connector->idx(), o.connector->id(), + o.connector->fullname()); + + for (const PropInfo& prop : o.conn_props) + fmt::print(" {}={}", prop.prop->name(), prop.val); + + fmt::print("\n Crtc {}/@{}", o.crtc->idx(), o.crtc->id()); + + for (const PropInfo& prop : o.crtc_props) + fmt::print(" {}={}", prop.prop->name(), prop.val); + + fmt::print(": {}\n", o.mode.to_string_long()); + + if (!o.legacy_fbs.empty()) { + auto fb = o.legacy_fbs[0]; + fmt::print(" Fb {} {}x{}-{}\n", fb->id(), fb->width(), fb->height(), pixel_format_to_fourcc_str(fb->format())); + } + + for (unsigned j = 0; j < o.planes.size(); ++j) { + const PlaneInfo& p = o.planes[j]; + auto fb = p.fbs[0]; + fmt::print(" Plane {}/@{}: {},{}-{}x{}", p.plane->idx(), p.plane->id(), + p.x, p.y, p.w, p.h); + for (const PropInfo& prop : p.props) + fmt::print(" {}={}", prop.prop->name(), prop.val); + fmt::print("\n"); + + fmt::print(" Fb {} {}x{}-{}\n", fb->id(), fb->width(), fb->height(), + pixel_format_to_fourcc_str(fb->format())); + if (s_print_crc) + fmt::print(" CRC16 {}\n", fb_crc(fb).c_str()); + } + } +} + +static void draw_test_patterns(const vector<OutputInfo>& outputs) +{ + for (const OutputInfo& o : outputs) { + for (auto fb : o.legacy_fbs) + draw_test_pattern(*fb, s_pattern_options); + + for (const PlaneInfo& p : o.planes) + for (auto fb : p.fbs) + draw_test_pattern(*fb, s_pattern_options); + } +} + +static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs) +{ + // Disable unused crtcs + for (Crtc* crtc : card.get_crtcs()) { + if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end()) + continue; + + crtc->disable_mode(); + } + + for (const OutputInfo& o : outputs) { + int r; + auto conn = o.connector; + auto crtc = o.crtc; + + for (const PropInfo& prop : o.conn_props) { + r = conn->set_prop_value(prop.prop, prop.val); + EXIT_IF(r, "failed to set connector property %s\n", prop.name.c_str()); + } + + for (const PropInfo& prop : o.crtc_props) { + r = crtc->set_prop_value(prop.prop, prop.val); + EXIT_IF(r, "failed to set crtc property %s\n", prop.name.c_str()); + } + + if (!o.legacy_fbs.empty()) { + auto fb = o.legacy_fbs[0]; + r = crtc->set_mode(conn, *fb, o.mode); + if (r) + fmt::print(stderr, "crtc->set_mode() failed for crtc {}: {}\n", + crtc->id(), strerror(-r)); + } + + for (const PlaneInfo& p : o.planes) { + for (const PropInfo& prop : p.props) { + r = p.plane->set_prop_value(prop.prop, prop.val); + EXIT_IF(r, "failed to set plane property %s\n", prop.name.c_str()); + } + + auto fb = p.fbs[0]; + r = crtc->set_plane(p.plane, *fb, + p.x, p.y, p.w, p.h, + 0, 0, fb->width(), fb->height()); + if (r) + fmt::print(stderr, "crtc->set_plane() failed for plane {}: {}\n", + p.plane->id(), strerror(-r)); + } + } +} + +static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs) +{ + int r; + + // XXX DRM framework doesn't allow moving an active plane from one crtc to another. + // See drm_atomic.c::plane_switching_crtc(). + // For the time being, try and disable all crtcs and planes here. + // Do not check the return value as some simple displays don't support the crtc being + // enabled but the primary plane being disabled. + + AtomicReq disable_req(card); + + // Disable unused crtcs + for (Crtc* crtc : card.get_crtcs()) { + //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end()) + // continue; + + disable_req.add(crtc, { + { "ACTIVE", 0 }, + }); + } + + // Disable unused planes + for (Plane* plane : card.get_planes()) + disable_req.add(plane, { + { "FB_ID", 0 }, + { "CRTC_ID", 0 }, + }); + + disable_req.commit_sync(true); + + // Keep blobs here so that we keep ref to them until we have committed the req + vector<unique_ptr<Blob>> blobs; + + AtomicReq req(card); + + for (const OutputInfo& o : outputs) { + auto conn = o.connector; + auto crtc = o.crtc; + + blobs.emplace_back(o.mode.to_blob(card)); + Blob* mode_blob = blobs.back().get(); + + req.add(conn, { + { "CRTC_ID", crtc->id() }, + }); + + for (const PropInfo& prop : o.conn_props) + req.add(conn, prop.prop, prop.val); + + req.add(crtc, { + { "ACTIVE", 1 }, + { "MODE_ID", mode_blob->id() }, + }); + + for (const PropInfo& prop : o.crtc_props) + req.add(crtc, prop.prop, prop.val); + + for (const PlaneInfo& p : o.planes) { + auto fb = p.fbs[0]; + + req.add(p.plane, { + { "FB_ID", fb->id() }, + { "CRTC_ID", crtc->id() }, + { "SRC_X", (p.view_x ?: 0) << 16 }, + { "SRC_Y", (p.view_y ?: 0) << 16 }, + { "SRC_W", (p.view_w ?: fb->width()) << 16 }, + { "SRC_H", (p.view_h ?: fb->height()) << 16 }, + { "CRTC_X", p.x }, + { "CRTC_Y", p.y }, + { "CRTC_W", p.w }, + { "CRTC_H", p.h }, + }); + + for (const PropInfo& prop : p.props) + req.add(p.plane, prop.prop, prop.val); + } + } + + r = req.test(true); + if (r) + EXIT("Atomic test failed: %d\n", r); + + r = req.commit_sync(true); + if (r) + EXIT("Atomic commit failed: %d\n", r); +} + +static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs) +{ + if (card.has_atomic()) + set_crtcs_n_planes_atomic(card, outputs); + else + set_crtcs_n_planes_legacy(card, outputs); +} + +static bool max_flips_reached; + +class FlipState : private PageFlipHandlerBase +{ +public: + FlipState(Card& card, const string& name, const vector<const OutputInfo*>& outputs) + : m_card(card), m_name(name), m_outputs(outputs), m_frame_num(0), m_flip_count(0) + { + } + + void start_flipping() + { + m_prev_frame = m_prev_print = std::chrono::steady_clock::now(); + m_slowest_frame = std::chrono::duration<float>::min(); + m_frame_num = 0; + queue_next(); + } + +private: + void handle_page_flip(uint32_t frame, double time) + { + /* + * We get flip event for each crtc in this flipstate. We can commit the next frames + * only after we've gotten the flip event for all crtcs + */ + if (++m_flip_count < m_outputs.size()) + return; + + m_frame_num++; + if (s_max_flips && m_frame_num >= s_max_flips) + max_flips_reached = true; + + auto now = std::chrono::steady_clock::now(); + + std::chrono::duration<float> diff = now - m_prev_frame; + if (diff > m_slowest_frame) + m_slowest_frame = diff; + + if (m_frame_num % 100 == 0) { + std::chrono::duration<float> fsec = now - m_prev_print; + fmt::print("Connector {}: fps {:.2f}, slowest {:.2f} ms\n", + m_name.c_str(), + 100.0 / fsec.count(), + m_slowest_frame.count() * 1000); + m_prev_print = now; + m_slowest_frame = std::chrono::duration<float>::min(); + } + + m_prev_frame = now; + + queue_next(); + } + + static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num) + { + return (frame_num * bar_speed) % (fb->width() - bar_width + 1); + } + + static void draw_bar(Framebuffer* fb, unsigned frame_num) + { + int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers); + int new_xpos = get_bar_pos(fb, frame_num); + + draw_color_bar(*fb, old_xpos, new_xpos, bar_width); + draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255)); + } + + static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o) + { + unsigned cur = frame_num % s_num_buffers; + + for (const PlaneInfo& p : o.planes) { + auto fb = p.fbs[cur]; + + draw_bar(fb, frame_num); + + req.add(p.plane, { + { "FB_ID", fb->id() }, + }); + } + } + + void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o) + { + unsigned cur = frame_num % s_num_buffers; + + if (!o.legacy_fbs.empty()) { + auto fb = o.legacy_fbs[cur]; + + draw_bar(fb, frame_num); + + int r = o.crtc->page_flip(*fb, this); + ASSERT(r == 0); + } + + for (const PlaneInfo& p : o.planes) { + auto fb = p.fbs[cur]; + + draw_bar(fb, frame_num); + + int r = o.crtc->set_plane(p.plane, *fb, + p.x, p.y, p.w, p.h, + 0, 0, fb->width(), fb->height()); + ASSERT(r == 0); + } + } + + void queue_next() + { + m_flip_count = 0; + + if (m_card.has_atomic()) { + AtomicReq req(m_card); + + for (auto o : m_outputs) + do_flip_output(req, m_frame_num, *o); + + int r = req.commit(this); + if (r) + EXIT("Flip commit failed: %d\n", r); + } else { + ASSERT(m_outputs.size() == 1); + do_flip_output_legacy(m_frame_num, *m_outputs[0]); + } + } + + Card& m_card; + string m_name; + vector<const OutputInfo*> m_outputs; + unsigned m_frame_num; + unsigned m_flip_count; + + chrono::steady_clock::time_point m_prev_print; + chrono::steady_clock::time_point m_prev_frame; + chrono::duration<float> m_slowest_frame; + + static const unsigned bar_width = 20; + static const unsigned bar_speed = 8; +}; + +static void main_flip(Card& card, const vector<OutputInfo>& outputs) +{ +// clang-tidy does not seem to handle FD_xxx macros +#ifndef __clang_analyzer__ + fd_set fds; + + FD_ZERO(&fds); + + int fd = card.fd(); + + vector<unique_ptr<FlipState>> flipstates; + + if (!s_flip_sync) { + for (const OutputInfo& o : outputs) { + auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o })); + flipstates.push_back(std::move(fs)); + } + } else { + vector<const OutputInfo*> ois; + + string name; + for (const OutputInfo& o : outputs) { + name += to_string(o.connector->idx()) + ","; + ois.push_back(&o); + } + + auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois)); + flipstates.push_back(std::move(fs)); + } + + for (unique_ptr<FlipState>& fs : flipstates) + fs->start_flipping(); + + auto deadline = chrono::steady_clock::now() + chrono::seconds(s_run_time); + + while (!max_flips_reached) { + int r; + + if (s_run_time == 0) + FD_SET(0, &fds); + FD_SET(fd, &fds); + + r = select(fd + 1, &fds, NULL, NULL, NULL); + if (r < 0) { + fmt::print(stderr, "select() failed with {}: {}\n", errno, strerror(errno)); + break; + } else if (FD_ISSET(0, &fds)) { + fmt::print(stderr, "Exit due to user-input\n"); + break; + } else if (FD_ISSET(fd, &fds)) { + card.call_page_flip_handlers(); + } + + if (s_run_time > 0 && chrono::steady_clock::now() >= deadline) + break; + } +#endif +} + +int main(int argc, char** argv) +{ + vector<Arg> output_args = parse_cmdline(argc, argv); + + Card card(s_device_path); + + if (!card.is_master()) + EXIT("Could not get DRM master permission. Card already in use?"); + + if (!card.has_atomic() && s_flip_sync) + EXIT("Synchronized flipping requires atomic modesetting"); + + ResourceManager resman(card); + + vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args); + + if (!s_flip_mode) + draw_test_patterns(outputs); + + print_outputs(outputs); + + set_crtcs_n_planes(card, outputs); + + if (s_run_time > 0) + fmt::print("running for {} seconds\n", s_run_time); + else + fmt::print("press enter to exit\n"); + + if (s_flip_mode) + main_flip(card, outputs); + else if (s_run_time > 0) + std::this_thread::sleep_for(std::chrono::seconds(s_run_time)); + else + getchar(); +} diff --git a/utils/kmstouch.cpp b/utils/kmstouch.cpp new file mode 100644 index 0000000..f4d4c2a --- /dev/null +++ b/utils/kmstouch.cpp @@ -0,0 +1,293 @@ +#include <cstdio> +#include <unistd.h> +#include <algorithm> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <cstring> + +#include <libevdev/libevdev.h> +#include <libevdev/libevdev-uinput.h> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +using namespace std; +using namespace kms; + +static const char* usage_str = + "Usage: kmstouch [OPTION]...\n\n" + "Simple touchscreen tester\n\n" + "Options:\n" + " --input=DEVICE DEVICE is the path to input device to open\n" + " --device=DEVICE DEVICE is the path to DRM card to open\n" + " -c, --connector=CONN CONN is <connector>\n" + "\n"; + +static void usage() +{ + puts(usage_str); +} + +static bool s_print_ev = false; + +static vector<pair<int32_t, int32_t>> s_coords; + +// axis -> min,max +static map<int, pair<int32_t, int32_t>> s_abs_map; +// axis -> value +static map<int, int32_t> s_abs_vals; + +static void print_abs_bits(struct libevdev* dev, int axis) +{ + const struct input_absinfo* abs; + + if (!libevdev_has_event_code(dev, EV_ABS, axis)) + return; + + abs = libevdev_get_abs_info(dev, axis); + + printf(" Value %6d\n", abs->value); + printf(" Min %6d\n", abs->minimum); + printf(" Max %6d\n", abs->maximum); + if (abs->fuzz) + printf(" Fuzz %6d\n", abs->fuzz); + if (abs->flat) + printf(" Flat %6d\n", abs->flat); + if (abs->resolution) + printf(" Resolution %6d\n", abs->resolution); +} + +static void print_code_bits(struct libevdev* dev, unsigned int type, unsigned int max) +{ + for (uint32_t i = 0; i <= max; i++) { + if (!libevdev_has_event_code(dev, type, i)) + continue; + + printf(" Event code %u (%s)\n", i, libevdev_event_code_get_name(type, i)); + if (type == EV_ABS) + print_abs_bits(dev, i); + } +} + +static void print_bits(struct libevdev* dev) +{ + printf("Supported events:\n"); + + for (uint32_t i = 0; i <= EV_MAX; i++) { + if (!libevdev_has_event_type(dev, i)) + continue; + + printf(" Event type %u (%s)\n", i, libevdev_event_type_get_name(i)); + + switch (i) { + case EV_KEY: + print_code_bits(dev, EV_KEY, KEY_MAX); + break; + case EV_REL: + print_code_bits(dev, EV_REL, REL_MAX); + break; + case EV_ABS: + print_code_bits(dev, EV_ABS, ABS_MAX); + break; + case EV_LED: + print_code_bits(dev, EV_LED, LED_MAX); + break; + } + } +} + +static void collect_current(struct libevdev* dev) +{ + for (uint32_t i = 0; i <= ABS_MAX; i++) { + if (!libevdev_has_event_code(dev, EV_ABS, i)) + continue; + + const struct input_absinfo* abs; + + abs = libevdev_get_abs_info(dev, i); + + s_abs_vals[i] = abs->value; + s_abs_map[i] = make_pair(abs->minimum, abs->maximum); + } +} + +static void print_props(struct libevdev* dev) +{ + printf("Properties:\n"); + + for (uint32_t i = 0; i <= INPUT_PROP_MAX; i++) { + if (!libevdev_has_property(dev, i)) + continue; + + printf(" Property type %u (%s)\n", i, libevdev_property_get_name(i)); + } +} + +static void handle_event(struct input_event& ev, DumbFramebuffer* fb) +{ + static vector<pair<uint16_t, int32_t>> s_event_vec; + + if (s_print_ev) + printf("%-6s %20s %6d\n", + libevdev_event_type_get_name(ev.type), + libevdev_event_code_get_name(ev.type, ev.code), + ev.value); + + switch (ev.type) { + case EV_ABS: + s_event_vec.emplace_back(ev.code, ev.value); + break; + + case EV_KEY: + s_event_vec.emplace_back(ev.code, ev.value); + break; + + case EV_SYN: + switch (ev.code) { + case SYN_REPORT: { + int32_t min_x = s_abs_map[ABS_X].first; + int32_t max_x = s_abs_map[ABS_X].second; + + int32_t min_y = s_abs_map[ABS_Y].first; + int32_t max_y = s_abs_map[ABS_Y].second; + + for (const auto& p : s_event_vec) { + switch (p.first) { + case ABS_X: + case ABS_Y: + s_abs_vals[p.first] = p.second; + break; + default: + break; + } + } + + int32_t abs_x = s_abs_vals[ABS_X]; + int32_t abs_y = s_abs_vals[ABS_Y]; + + int32_t x = (abs_x - min_x) * (fb->width() - 1) / (max_x - min_x); + int32_t y = (abs_y - min_y) * (fb->height() - 1) / (max_y - min_y); + + printf("%d, %d -> %d, %d\n", abs_x, abs_y, x, y); + + draw_rgb_pixel(*fb, x, y, RGB(255, 255, 255)); + + s_event_vec.clear(); + + if (s_print_ev) + printf("----\n"); + break; + } + + default: + EXIT("Unhandled syn event code %u\n", ev.code); + break; + } + + break; + + default: + EXIT("Unhandled event type %u\n", ev.type); + break; + } +} + +int main(int argc, char** argv) +{ + string drm_dev_path = "/dev/dri/card0"; + string input_dev_path = "/dev/input/event0"; + string conn_name; + + OptionSet optionset = { + Option("i|input=", [&input_dev_path](string s) { + input_dev_path = s; + }), + Option("|device=", [&drm_dev_path](string s) { + drm_dev_path = s; + }), + Option("c|connector=", [&conn_name](string s) { + conn_name = s; + }), + Option("h|help", []() { + usage(); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + usage(); + exit(-1); + } + + struct libevdev* dev = nullptr; + + int fd = open(input_dev_path.c_str(), O_RDONLY | O_NONBLOCK); + FAIL_IF(fd < 0, "Failed to open input device %s: %s\n", input_dev_path.c_str(), strerror(errno)); + int rc = libevdev_new_from_fd(fd, &dev); + FAIL_IF(rc < 0, "Failed to init libevdev (%s)\n", strerror(-rc)); + + printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); + printf("Input device ID: bus %#x vendor %#x product %#x\n", + libevdev_get_id_bustype(dev), + libevdev_get_id_vendor(dev), + libevdev_get_id_product(dev)); + + if (!libevdev_has_event_type(dev, EV_ABS) || + !libevdev_has_event_code(dev, EV_KEY, BTN_TOUCH)) { + printf("This device does not look like a mouse\n"); + exit(1); + } + + print_bits(dev); + print_props(dev); + + collect_current(dev); + + Card card(drm_dev_path); + ResourceManager resman(card); + + auto pixfmt = PixelFormat::XRGB8888; + + Connector* conn = resman.reserve_connector(conn_name); + Crtc* crtc = resman.reserve_crtc(conn); + Plane* plane = resman.reserve_overlay_plane(crtc, pixfmt); + + Videomode mode = conn->get_default_mode(); + + uint32_t w = mode.hdisplay; + uint32_t h = mode.vdisplay; + + auto fb = new DumbFramebuffer(card, w, h, pixfmt); + + AtomicReq req(card); + + req.add(plane, "CRTC_ID", crtc->id()); + req.add(plane, "FB_ID", fb->id()); + + req.add(plane, "CRTC_X", 0); + req.add(plane, "CRTC_Y", 0); + req.add(plane, "CRTC_W", w); + req.add(plane, "CRTC_H", h); + + req.add(plane, "SRC_X", 0); + req.add(plane, "SRC_Y", 0); + req.add(plane, "SRC_W", w << 16); + req.add(plane, "SRC_H", h << 16); + + int r = req.commit_sync(); + FAIL_IF(r, "initial plane setup failed"); + + do { + struct input_event ev { + }; + rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc == 0) + handle_event(ev, fb); + + } while (rc == 1 || rc == 0 || rc == -EAGAIN); + + delete fb; +} diff --git a/utils/kmsview.cpp b/utils/kmsview.cpp new file mode 100644 index 0000000..7cee024 --- /dev/null +++ b/utils/kmsview.cpp @@ -0,0 +1,121 @@ +#include <cstdio> +#include <fstream> +#include <unistd.h> +#include <cassert> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +using namespace std; +using namespace kms; + +static void read_frame(ifstream& is, DumbFramebuffer* fb, Crtc* crtc, Plane* plane) +{ + for (unsigned i = 0; i < fb->num_planes(); ++i) + is.read(reinterpret_cast<char*>(fb->map(i)), fb->size(i)); + + unsigned w = min(crtc->width(), fb->width()); + unsigned h = min(crtc->height(), fb->height()); + + int r = crtc->set_plane(plane, *fb, + 0, 0, w, h, + 0, 0, fb->width(), fb->height()); + + ASSERT(r == 0); +} + +static const char* usage_str = + "Usage: kmsview [options] <file> <width> <height> <fourcc>\n\n" + "Options:\n" + " -c, --connector <name> Output connector\n" + " -t, --time <ms> Milliseconds to sleep between frames\n"; + +static void usage() +{ + puts(usage_str); +} + +int main(int argc, char** argv) +{ + uint32_t time = 0; + string dev_path; + string conn_name; + + OptionSet optionset = { + Option("c|connector=", [&conn_name](string s) { + conn_name = s; + }), + Option("|device=", [&dev_path](string s) { + dev_path = s; + }), + Option("t|time=", [&time](const string& str) { + time = stoul(str); + }), + Option("h|help", []() { + usage(); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + vector<string> params = optionset.params(); + + if (params.size() != 4) { + usage(); + exit(-1); + } + + string filename = params[0]; + uint32_t w = stoi(params[1]); + uint32_t h = stoi(params[2]); + string modestr = params[3]; + + auto pixfmt = fourcc_str_to_pixel_format(modestr); + + ifstream is(filename, ifstream::binary); + + is.seekg(0, std::ios::end); + unsigned fsize = is.tellg(); + is.seekg(0); + + Card card(dev_path); + ResourceManager res(card); + + auto conn = res.reserve_connector(conn_name); + auto crtc = res.reserve_crtc(conn); + auto plane = res.reserve_overlay_plane(crtc, pixfmt); + FAIL_IF(!plane, "available plane not found"); + + auto fb = new DumbFramebuffer(card, w, h, pixfmt); + + unsigned frame_size = 0; + for (unsigned i = 0; i < fb->num_planes(); ++i) + frame_size += fb->size(i); + + assert(frame_size); + + unsigned num_frames = fsize / frame_size; + printf("file size %u, frame size %u, frames %u\n", fsize, frame_size, num_frames); + + for (unsigned i = 0; i < num_frames; ++i) { + printf("frame %u", i); + fflush(stdout); + read_frame(is, fb, crtc, plane); + if (!time) { + getchar(); + } else { + usleep(time * 1000); + printf("\n"); + } + } + + is.close(); + + if (time) { + printf("press enter to exit\n"); + getchar(); + } + + delete fb; +} diff --git a/utils/meson.build b/utils/meson.build new file mode 100644 index 0000000..54021d3 --- /dev/null +++ b/utils/meson.build @@ -0,0 +1,26 @@ +if not get_option('utils') + utils_enabled = false + subdir_done() +endif + +if not get_option('libutils') + utils_enabled = false + subdir_done() +endif + +utils_enabled = true + +common_deps = [ libkmsxx_dep, libkmsxxutil_dep ] + +libevdev_dep = dependency('libevdev', required : false) + +executable('kmstest', 'kmstest.cpp', dependencies : [ common_deps ], install : true) +executable('kmsview', 'kmsview.cpp', dependencies : [ common_deps ], install : false) +executable('kmsprint', 'kmsprint.cpp', dependencies : [ common_deps ], install : true) +executable('fbtest', 'fbtest.cpp', dependencies : [ common_deps ], install : true) +executable('kmscapture', 'kmscapture.cpp', dependencies : [ common_deps ], install : false) +executable('kmsblank', 'kmsblank.cpp', dependencies : [ common_deps ], install : true) + +if libevdev_dep.found() + executable('kmstouch', 'kmstouch.cpp', dependencies : [ common_deps, libevdev_dep ], install : false) +endif |
