Discussion:
[RFC 00/12] Bugfixes for CONFIG_VT=n
(too old to reply)
David Herrmann
2013-11-27 18:48:35 UTC
Permalink
Hi

So booting without VTs is still a horrible experience on desktop-linux. If
anyone tried, they were presumably running away in tears. This series is a new
attempt to fix that.

The first 4 patches are just small fixes/helpers. Patch 5 to 8 introduce sd-gfx
and patch 9-11 add tests/examples for sd-gfx. The final patch adds a first user
of sd-gfx to replace the kernel-internal terminal-emulator. The commit-msgs
should explain each change at length so I won't repeat it here.

To anyone interested in testing: If you apply this series, you can run
"./test-kbd" to test keyboard handling, "./test-gfx" to test graphics rendering
and "./systemd-consoled" to run the terminal-emulator. Note that you must run
all these from *within* an existing logind-session. That usually means from a
TEXT-VT. Also note that all these programs currently ignore the underlying VT
entirely (in case you run with CONFIG_VT=y). This has the side-effect, that all
keystrokes go to both, the VT and the application. This will obviously be
changed in later versions, but I wanted to avoid any VT calls in these helpers.
I currently work on logind to fix that.

I had to strip the font-patches due to ML size-constraints. The full series is
always available on the-often-rebased-console-branch at:
http://cgit.freedesktop.org/~dvdhrm/systemd/log/?h=console

Notes on where this is going, and *why*, below..

Happy Testing!
David


What is wrong with CONFIG_VT?
=============================

The VT layer in the kernel implements a rather complex keyboard layer (similar
to XKB, but less powerful), implements hard-coded device hotplug filters, input
handling, graphics-pipeline handling, font-rendering, terminal-emulation,
cursor handling and more. All this is used to render a basic text-UI *in the
kernel*!
However, moving UI handling into the kernel has a lot of disadvantages (the
same reason why user-space exists.. look it up). For completeness, some rather
obvious reasons are:
- non-swappable memory (even if you have no swap-partition, user-space apps
can avoid loading all fonts/keymaps into memory, which kernel-space cannot)
- security critical (any bug in the UI layer may allow *kernel-level*
privileges to attackers)
- stability (user-space apps can be easily restarted on failure; in
kernel-space, all kinds of memory may get overwritten..)
- code-duplication (we already have all this code in user-space, why copy it
into kernel-space?)
- simplicity (we wanna keep the kernel simple and slim, which prevents adding
more complex features like fully internationalized fonts and keymaps, which
are *required* for emergency consoles with given keyboards)
- configurability/controllability (ever tried assigning more/less CPU-time
to a given VT?)
- ...

So what reasons exist to have a UI layer in kernel-space? I only found 3
reasons, if someone knows more, let me know. I highly doubt there's more..
1) Early boot debugging
2) Panic/Oops screen
3) kdb kernel debugging
For 1) and 2) we have an experimental fblog/drmlog kernel module which just
prints the kernel log to all screens. This only requires ASCII glyphs and raw
access to DRM FBs. All the VT cruft can be dropped..
For 3): You're welcome to enable CONFIG_VT if you need a terminal-emulator for
kernel-debugging. My experience is that a serial-console is far more helpful and
reliable and avoids calling DRM modesetting in atomic-context (ugh? This fact
really makes me doubt anyone uses kdb.. Use kgdb!).


Long-term plan
==============

So the long-term plan for this series is to set CONFIG_VT=n. This actually
already works and you can boot your machine and start sessions like kmscon.
Even Xorg works if you apply a one-line patch. However, without VTs, legacy
sessions like Xorg will run in exclusive mode. To allow session switching, you
need systemd-logind. But even session-switching is already implemented. Though,
no real application uses that, yet (except for weston and some example code).

So most of the work is to fix all these applications to support VT-less mode.
This is already ongoing and will work soon (hopefully..).

So what does this series implement?
Well, if you run without VTs, you lack any kind of fallback console or fallback
login-screen. To guarantee that even without VTs there's a system-console and
login-screen, this series adds the most basic implementations of those to
systemd. This will *never* turn into a full-blown full-featured console. No
eye-candy, no useless features, NO BACKGROUND IMAGES!!!
The idea is to provide a fallback in systemd. If you want a fancy console with
more features, install kmscon and it will simply replace the systemd-fallback.
However, that means more memory-footprint, more CPU-requirements and a lot more
dependencies.

sd-gfx is a helper library in systemd to unify all the graphics, screen and font
handling. All following applications will use it, including:
(daemon-names are subject to change!)
- systemd-consoled: A systemd console (basic terminal emulator) used as
fallback session/console.
- systemd-splashd: A boot-splash for initrds. This will not include eye-candy
but is only required to accept password-input for
encrypted root-partitions. Use Plymouth if you want
eye-candy!
- systemd-welcomed: A login-screen to spawn sessions. This basically replaces
/sbin/agetty and /bin/login on a VT. After login it spawns
systemd-consoled.
- systemd-er: The emergency-room helper. Basically an even-more stripped down
systemd-consoled designed for developers. It avoids
session-management, seat-assignments, privilege-separation and
is just an hopefully-always-working-emergency-console. It's not
enabled by default. Think of it as a userspace-variant of
"magic sysrq".


This is stupid, why do you..
============================

..add so many dependencies and code to systemd?
You should consider that currently all that code is part of the *kernel*.
All this does is moving it out of the kernel. The first place that comes to
mind is systemd (and I couldn't imagine a better place, can you?).
Knowing that systemd already depends on the kernel, technically this doesn't
increase the codebase of systemd at all.

..kill my beloved kernel-console / linux-console?
I don't. Just set CONFIG_VT=y.

..duplicate kmscon?
Well, I wrote that and I consider it a successfull research project. Now
it's time to write something useful based on the lessons learned with
kmscon. No first attempt ever succeeds, right?

..miss the fact that a kernel-console is more reliable than user-space?
It's not. Why do you think that? You need a running shell to do anything
useful with a console. Where do you think this shell runs? (Hint: it's not
kernel-space.. and not outer space.. I wonder what "space" is left? Oh,
none, because your in-kernel fonts are non-swappable!)

..kill my panic/oops screen?
I don't. See above. I wrote fblog/drmlog for that.

..change a running system?
To make it better!

..add useful features like full-BMP-range Unicode-fonts, properly
internationalized keymaps, readable fonts on high-DPI displays,
access-separation, accessibility helpers and more to the system console?
Wait.. that's obvious, isn't it?


Besides, coding is fun!
David

David Herrmann (12):
event: allow EPOLLET as event flag
ring: add basic ring-buffer helper
bus: add two new bus_*_map_*_properties() helpers
build: add target to link binary sources
gfx: add sd-gfx library with unifont section
gfx: add keyboard layer
gfx: add graphics layer
gfx: add monitor
gfx: add kbd test
gfx: add graphics test
gfx: add unbuilt GL test
console: add systemd-consoled

.gitignore | 4 +
Makefile.am | 129 +
configure.ac | 38 +
make-unifont.py | 138 +
src/console/Makefile | 1 +
src/console/consoled-pty.c | 391 +
src/console/consoled-screen.c | 170 +
src/console/consoled-terminal.c | 371 +
src/console/consoled.c | 278 +
src/console/consoled.h | 128 +
src/libsystemd-bus/bus-util.c | 87 +-
src/libsystemd-bus/bus-util.h | 6 +
src/libsystemd-bus/sd-event.c | 4 +-
src/libsystemd-gfx/.gitignore | 1 +
src/libsystemd-gfx/Makefile | 1 +
src/libsystemd-gfx/gfx-drm.c | 2551 ++
src/libsystemd-gfx/gfx-kbd.c | 629 +
src/libsystemd-gfx/gfx-monitor.c | 1767 ++
src/libsystemd-gfx/gfx-unifont.c | 273 +
src/libsystemd-gfx/test-gfx.c | 302 +
src/libsystemd-gfx/test-gl.c | 342 +
src/libsystemd-gfx/test-kbd.c | 314 +
src/libsystemd-gfx/unifont.bin | Bin 0 -> 2162688 bytes
src/libsystemd-gfx/unifont.hex | 63488 +++++++++++++++++++++++++++++++++++++
src/shared/ring.c | 213 +
src/shared/ring.h | 54 +
src/systemd/sd-gfx.h | 405 +
27 files changed, 72058 insertions(+), 27 deletions(-)
create mode 100755 make-unifont.py
create mode 120000 src/console/Makefile
create mode 100644 src/console/consoled-pty.c
create mode 100644 src/console/consoled-screen.c
create mode 100644 src/console/consoled-terminal.c
create mode 100644 src/console/consoled.c
create mode 100644 src/console/consoled.h
create mode 100644 src/libsystemd-gfx/.gitignore
create mode 120000 src/libsystemd-gfx/Makefile
create mode 100644 src/libsystemd-gfx/gfx-drm.c
create mode 100644 src/libsystemd-gfx/gfx-kbd.c
create mode 100644 src/libsystemd-gfx/gfx-monitor.c
create mode 100644 src/libsystemd-gfx/gfx-unifont.c
create mode 100644 src/libsystemd-gfx/test-gfx.c
create mode 100644 src/libsystemd-gfx/test-gl.c
create mode 100644 src/libsystemd-gfx/test-kbd.c
create mode 100644 src/libsystemd-gfx/unifont.bin
create mode 100644 src/libsystemd-gfx/unifont.hex
create mode 100644 src/shared/ring.c
create mode 100644 src/shared/ring.h
create mode 100644 src/systemd/sd-gfx.h
--
1.8.4.2
David Herrmann
2013-11-27 18:48:37 UTC
Permalink
This adds a very straightforward ring-buffer implementation to
libsystemd-shared. Ring-buffers allow pushing data to, and pulling data
from, both ends of a buffer without modifying existing/remaining buffer
content. This is very useful for network buffers and asynchronous writes.

This implementation only contains the most basic functions to push data
onto the end of the buffer and pull data from the front. More helpers may
be added once needed.
---
Makefile.am | 2 +
src/shared/ring.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/ring.h | 54 ++++++++++++++
3 files changed, 269 insertions(+)
create mode 100644 src/shared/ring.c
create mode 100644 src/shared/ring.h

diff --git a/Makefile.am b/Makefile.am
index 0119751..4aa2bdf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,6 +768,8 @@ libsystemd_shared_la_SOURCES = \
src/shared/net-util.h \
src/shared/errno-list.c \
src/shared/errno-list.h \
+ src/shared/ring.h \
+ src/shared/ring.c \
src/shared/syscall-list.c \
src/shared/syscall-list.h

diff --git a/src/shared/ring.c b/src/shared/ring.c
new file mode 100644
index 0000000..f60f098
--- /dev/null
+++ b/src/shared/ring.c
@@ -0,0 +1,213 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "ring.h"
+#include "util.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * Resize ring-buffer to size @nsize. @nsize must be a power-of-2, otherwise
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
+ } else if (r->end > r->start) {
+ memcpy(buf, &r->buf[r->start], r->end - r->start);
+
+ r->end -= r->start;
+ r->start = 0;
+ } else {
+ memcpy(buf, &r->buf[r->start], r->size - r->start);
+ memcpy(&buf[r->size - r->start], r->buf, r->end);
+
+ r->end += r->size - r->start;
+ r->start = 0;
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+
+ return 0;
+}
+
+/* Compute next higher power-of-2 of @v. Returns 4096 in case v is 0. */
+static size_t ring_pow2(size_t v) {
+ size_t i;
+
+ if (!v)
+ return 4096;
+
+ --v;
+
+ for (i = 1; i < 8 * sizeof(size_t); i *= 2)
+ v |= v >> i;
+
+ return ++v;
+}
+
+/*
+ * Resize ring-buffer to provide enough room for @add bytes of new data. This
+ * resizes the buffer if it is too small. It returns -ENOMEM on OOM and 0 on
+ * success.
+ */
+static int ring_grow(Ring *r, size_t add) {
+ size_t len;
+
+ /*
+ * Note that "end == start" means "empty buffer". Hence, we can never
+ * fill the last byte of a buffer. That means, we must account for an
+ * additional byte here ("end == start"-byte).
+ */
+
+ if (r->end < r->start)
+ len = r->start - r->end;
+ else
+ len = r->start + r->size - r->end;
+
+ /* don't use ">=" as "end == start" would be ambigious */
+ if (len > add)
+ return 0;
+
+ /* +1 for additional "end == start" byte */
+ len = r->size + add - len + 1;
+ len = ring_pow2(len);
+
+ if (len <= r->size)
+ return -ENOMEM;
+
+ return ring_resize(r, len);
+}
+
+/*
+ * Push @len bytes from @u8 into the ring buffer. The buffer is resized if it
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int ring_push(Ring *r, const char *u8, size_t len) {
+ int err;
+ size_t l;
+
+ err = ring_grow(r, len);
+ if (err < 0)
+ return err;
+
+ if (r->start <= r->end) {
+ l = r->size - r->end;
+ if (l > len)
+ l = len;
+
+ memcpy(&r->buf[r->end], u8, l);
+ r->end = RING_MASK(r, r->end + l);
+
+ len -= l;
+ u8 += l;
+ }
+
+ if (!len)
+ return 0;
+
+ memcpy(&r->buf[r->end], u8, len);
+ r->end = RING_MASK(r, r->end + len);
+
+ return 0;
+}
+
+/*
+ * Remove @len bytes from the start of the ring-buffer. Note that we protect
+ * against overflows so removing more bytes than available is safe.
+ */
+void ring_pull(Ring *r, size_t len) {
+ size_t l;
+
+ if (r->start > r->end) {
+ l = r->size - r->start;
+ if (l > len)
+ l = len;
+
+ r->start = RING_MASK(r, r->start + l);
+ len -= l;
+ }
+
+ if (!len)
+ return;
+
+ l = r->end - r->start;
+ if (l > len)
+ l = len;
+
+ r->start = RING_MASK(r, r->start + l);
+}
+
+/*
+ * Get data pointers for current ring-buffer data. @vec must be an array of 2
+ * iovec objects. They are filled according to the data available in the
+ * ring-buffer. 0, 1 or 2 is returned according to the number of iovec objects
+ * that were filled (0 meaning buffer is empty).
+ *
+ * Hint: "struct iovec" is defined in <sys/uio.h> and looks like this:
+ * struct iovec {
+ * void *iov_base;
+ * size_t iov_len;
+ * };
+ */
+size_t ring_peek(Ring *r, struct iovec *vec) {
+ if (r->end > r->start) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->end - r->start;
+ }
+ return 1;
+ } else if (r->end < r->start) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->size - r->start;
+ vec[1].iov_base = r->buf;
+ vec[1].iov_len = r->end;
+ }
+ return r->end ? 2 : 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/shared/ring.h b/src/shared/ring.h
new file mode 100644
index 0000000..d2cd016
--- /dev/null
+++ b/src/shared/ring.h
@@ -0,0 +1,54 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* Pretty straightforward ring-buffer implementation. */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include "util.h"
+
+typedef struct Ring Ring;
+
+struct Ring {
+ char *buf;
+ size_t size;
+ size_t start;
+ size_t end;
+};
+
+void ring_flush(Ring *r);
+void ring_clear(Ring *r);
+
+int ring_push(Ring *r, const char *u8, size_t len);
+void ring_pull(Ring *r, size_t len);
+size_t ring_peek(Ring *r, struct iovec *vec);
+
+static inline size_t ring_length(Ring *r) {
+ if (r->end > r->start)
+ return r->end - r->start;
+ else if (r->end < r->start)
+ return (r->size - r->start) + r->end;
+ else
+ return 0;
+}
--
1.8.4.2
Lennart Poettering
2013-11-27 21:53:44 UTC
Permalink
Post by David Herrmann
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
zero(*r) is a tiny bit nicer.
Post by David Herrmann
+}
+
+/*
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
Hmm, "char" suggests a bit that this is about text. But it's mostly raw
bytes, right? So I'd always use uint8_t for these things, it feels so
much "rawer"... Not that it would matter much...
Post by David Herrmann
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
Hmm, so if end and start match the buffer is empty? So you can never
fill it entirely? Or am I missing something?

I'd always maintain start index + fill level instead of a start
index + end index, to avoid the confusion regarding the fill-level...
Post by David Herrmann
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int ring_push(Ring *r, const char *u8, size_t len) {
So, here you call the parameter u8 suggesting unsigned bytes, but you
use char, which indicates a text string, and is
signed... Confusing. I'd recommend using "const void *" here by default,
since all types implicitly downgrade to "const void*" without casting...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:19:06 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 10:53 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
zero(*r) is a tiny bit nicer.
Post by David Herrmann
+}
+
+/*
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
Hmm, "char" suggests a bit that this is about text. But it's mostly raw
bytes, right? So I'd always use uint8_t for these things, it feels so
much "rawer"... Not that it would matter much...
Yepp, uint8_t it is.
Post by Lennart Poettering
Post by David Herrmann
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
Hmm, so if end and start match the buffer is empty? So you can never
fill it entirely? Or am I missing something?
I'd always maintain start index + fill level instead of a start
index + end index, to avoid the confusion regarding the fill-level...
Well, start+end is how ring-buffers were implemented historically. I
think the reason is that "wrapping over" is easier to calculate for
indices than for lengths (you cannot do: r->len = (r->len + 1) &
RING_MASK). Even all examples on wikipedia use two indices
(http://en.wikipedia.org/wiki/Circular_buffer). And all kernel
ring-buffers use start/end..

Also note that the one special byte is not "unused". It cannot be
filled, in case the buffer is full, that's right. But it's used during
normal buffer-operation if the occupied space "moves along" the
buffer.

If you can find me circular/ring-buffers that use start+len, I can fix
that up. Otherwise, I'd rather keep this close to existing
implementations. Besides, a lot of "wrapping calculations" get easier
if start/end are restricted by RING_MASK, which wouldn't be the case
for "len".
Post by Lennart Poettering
Post by David Herrmann
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int ring_push(Ring *r, const char *u8, size_t len) {
So, here you call the parameter u8 suggesting unsigned bytes, but you
use char, which indicates a text string, and is
signed... Confusing. I'd recommend using "const void *" here by default,
since all types implicitly downgrade to "const void*" without casting...
I usually use "u8" for "utf8".. no idea where that came from. But yes,
void* makes sense for input/output and uint8_t* for internal
operations.

Thanks
David
Lennart Poettering
2013-12-11 00:39:38 UTC
Permalink
Post by David Herrmann
Post by Lennart Poettering
Hmm, so if end and start match the buffer is empty? So you can never
fill it entirely? Or am I missing something?
I'd always maintain start index + fill level instead of a start
index + end index, to avoid the confusion regarding the fill-level...
Well, start+end is how ring-buffers were implemented historically. I
think the reason is that "wrapping over" is easier to calculate for
indices than for lengths (you cannot do: r->len = (r->len + 1) &
RING_MASK). Even all examples on wikipedia use two indices
(http://en.wikipedia.org/wiki/Circular_buffer). And all kernel
ring-buffers use start/end..
Well, you only need the wrapping over for the reading side, the writing
side never needs that.

int append(..., length, ...) {
...
if (current_length + length > MAX_LENGTH)
return -ENOBUFS;
...
current_length += length;
...
}

int remove(..., length, ...) {
...
if (length > current_length)
return -EAGAIN;
...
rindex = (rindex + length) & RING_MASK;
current_length -= length;
...
}

Lennart
--
Lennart Poettering, Red Hat
Lucas De Marchi
2013-11-28 00:27:06 UTC
Permalink
Post by David Herrmann
This adds a very straightforward ring-buffer implementation to
libsystemd-shared. Ring-buffers allow pushing data to, and pulling data
from, both ends of a buffer without modifying existing/remaining buffer
content. This is very useful for network buffers and asynchronous writes.
This implementation only contains the most basic functions to push data
onto the end of the buffer and pull data from the front. More helpers may
be added once needed.
---
Makefile.am | 2 +
src/shared/ring.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/ring.h | 54 ++++++++++++++
3 files changed, 269 insertions(+)
create mode 100644 src/shared/ring.c
create mode 100644 src/shared/ring.h
diff --git a/Makefile.am b/Makefile.am
index 0119751..4aa2bdf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,6 +768,8 @@ libsystemd_shared_la_SOURCES = \
src/shared/net-util.h \
src/shared/errno-list.c \
src/shared/errno-list.h \
+ src/shared/ring.h \
+ src/shared/ring.c \
src/shared/syscall-list.c \
src/shared/syscall-list.h
diff --git a/src/shared/ring.c b/src/shared/ring.c
new file mode 100644
index 0000000..f60f098
--- /dev/null
+++ b/src/shared/ring.c
@@ -0,0 +1,213 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "ring.h"
+#include "util.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
+ } else if (r->end > r->start) {
+ memcpy(buf, &r->buf[r->start], r->end - r->start);
+
+ r->end -= r->start;
+ r->start = 0;
+ } else {
+ memcpy(buf, &r->buf[r->start], r->size - r->start);
+ memcpy(&buf[r->size - r->start], r->buf, r->end);
+
+ r->end += r->size - r->start;
+ r->start = 0;
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+
+ return 0;
+}
+
+static size_t ring_pow2(size_t v) {
+ size_t i;
+
+ if (!v)
+ return 4096;
+
+ --v;
+
+ for (i = 1; i < 8 * sizeof(size_t); i *= 2)
+ v |= v >> i;
+
+ return ++v;
If you are interested, take a look in
https://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/?id=3ba7f59e84857eb4dbe56a68fc7a3ffe8a650393
for a shorter and faster version of this.


Lucas De Marchi
David Herrmann
2013-11-28 08:19:44 UTC
Permalink
Hi

On Thu, Nov 28, 2013 at 1:27 AM, Lucas De Marchi
Post by Lucas De Marchi
Post by David Herrmann
This adds a very straightforward ring-buffer implementation to
libsystemd-shared. Ring-buffers allow pushing data to, and pulling data
from, both ends of a buffer without modifying existing/remaining buffer
content. This is very useful for network buffers and asynchronous writes.
This implementation only contains the most basic functions to push data
onto the end of the buffer and pull data from the front. More helpers may
be added once needed.
---
Makefile.am | 2 +
src/shared/ring.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/ring.h | 54 ++++++++++++++
3 files changed, 269 insertions(+)
create mode 100644 src/shared/ring.c
create mode 100644 src/shared/ring.h
diff --git a/Makefile.am b/Makefile.am
index 0119751..4aa2bdf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,6 +768,8 @@ libsystemd_shared_la_SOURCES = \
src/shared/net-util.h \
src/shared/errno-list.c \
src/shared/errno-list.h \
+ src/shared/ring.h \
+ src/shared/ring.c \
src/shared/syscall-list.c \
src/shared/syscall-list.h
diff --git a/src/shared/ring.c b/src/shared/ring.c
new file mode 100644
index 0000000..f60f098
--- /dev/null
+++ b/src/shared/ring.c
@@ -0,0 +1,213 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "ring.h"
+#include "util.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
+ } else if (r->end > r->start) {
+ memcpy(buf, &r->buf[r->start], r->end - r->start);
+
+ r->end -= r->start;
+ r->start = 0;
+ } else {
+ memcpy(buf, &r->buf[r->start], r->size - r->start);
+ memcpy(&buf[r->size - r->start], r->buf, r->end);
+
+ r->end += r->size - r->start;
+ r->start = 0;
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+
+ return 0;
+}
+
+static size_t ring_pow2(size_t v) {
+ size_t i;
+
+ if (!v)
+ return 4096;
+
+ --v;
+
+ for (i = 1; i < 8 * sizeof(size_t); i *= 2)
+ v |= v >> i;
+
+ return ++v;
If you are interested, take a look in
https://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/?id=3ba7f59e84857eb4dbe56a68fc7a3ffe8a650393
for a shorter and faster version of this.
Yepp, that one looks good. Didn't know there was a builtin to count
leading zeros.

Thanks
David
David Herrmann
2013-11-27 18:48:36 UTC
Permalink
EPOLLET enables edge-triggered mode (see epoll(7) for more). For most
use-cases, level-triggered is just fine, but for master-TTYs we need
edge-triggered to catch EPOLLHUP. master-TTYs signal EPOLLHUP if no client
is connected, but a client may connect some time later (same happens
during vhangup(2)).

However, epoll doesn't allow masking EPOLLHUP so it's signaled constantly.
To avoid this, edge-triggered mode is needed.
---
src/libsystemd-bus/sd-event.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libsystemd-bus/sd-event.c b/src/libsystemd-bus/sd-event.c
index 6a6581b..b5ddf71 100644
--- a/src/libsystemd-bus/sd-event.c
+++ b/src/libsystemd-bus/sd-event.c
@@ -584,7 +584,7 @@ _public_ int sd_event_add_io(

assert_return(e, -EINVAL);
assert_return(fd >= 0, -EINVAL);
- assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+ assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(callback, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
@@ -1022,7 +1022,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)

assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
- assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+ assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_pid_changed(s->event), -ECHILD);
--
1.8.4.2
Lennart Poettering
2013-11-27 21:46:09 UTC
Permalink
On Wed, 27.11.13 19:48, David Herrmann (***@gmail.com) wrote:

Looks obvious. Applied.
Post by David Herrmann
EPOLLET enables edge-triggered mode (see epoll(7) for more). For most
use-cases, level-triggered is just fine, but for master-TTYs we need
edge-triggered to catch EPOLLHUP. master-TTYs signal EPOLLHUP if no client
is connected, but a client may connect some time later (same happens
during vhangup(2)).
However, epoll doesn't allow masking EPOLLHUP so it's signaled constantly.
To avoid this, edge-triggered mode is needed.
---
src/libsystemd-bus/sd-event.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/libsystemd-bus/sd-event.c b/src/libsystemd-bus/sd-event.c
index 6a6581b..b5ddf71 100644
--- a/src/libsystemd-bus/sd-event.c
+++ b/src/libsystemd-bus/sd-event.c
@@ -584,7 +584,7 @@ _public_ int sd_event_add_io(
assert_return(e, -EINVAL);
assert_return(fd >= 0, -EINVAL);
- assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+ assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(callback, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
@@ -1022,7 +1022,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
- assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+ assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_pid_changed(s->event), -ECHILD);
Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-27 18:48:38 UTC
Permalink
This splits the core of bus_map_all_properties() out into a new helper
called bus_message_map_all_properties(). Instead of sending a blocking
dbus call, this helper takes the response message as argument and parses
it. So the normal use-case is to send an async GetAll() request and once
you get the response, pass it to the helper to map the properties.

The existing bus_map_all_properties() helper is now just a small wrapper
around sd_bus_call_method() and the new helper.

Furthermore, a second helper is added which parses "PropertiesChanged"
dbus signals. Whenever you get such a signal, you can pass the message to
a new helper called bus_message_map_properties_changed(). It parses the
PropertiesChanged payload for changed properties and maps them. In case
the payload only contains invalidation-requests and no new values, this
functions returns the number of successfully mapped properties that were
invalidated. Thus, if it returns >0, you should send a GetAll() or Get()
request.

Both helpers allow very convenient handling of dbus-properties in a
non-blocking fashion. This is required for graphics-applications and other
programs that need low-latency responses. In all other cases, the existing
blocking bus_map_all_properties() helper should be enough.
---
src/libsystemd-bus/bus-util.c | 87 ++++++++++++++++++++++++++++++-------------
src/libsystemd-bus/bus-util.h | 6 +++
2 files changed, 68 insertions(+), 25 deletions(-)

diff --git a/src/libsystemd-bus/bus-util.c b/src/libsystemd-bus/bus-util.c
index 2daf8c1..0e520c1 100644
--- a/src/libsystemd-bus/bus-util.c
+++ b/src/libsystemd-bus/bus-util.c
@@ -838,31 +838,12 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
return r;
}

-int bus_map_all_properties(sd_bus *bus,
- const char *destination,
- const char *path,
- const struct bus_properties_map *map,
- void *userdata) {
- _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+int bus_message_map_all_properties(sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;

- assert(bus);
- assert(destination);
- assert(path);
- assert(map);
-
- r = sd_bus_call_method( bus,
- destination,
- path,
- "org.freedesktop.DBus.Properties",
- "GetAll",
- &error,
- &m,
- "s", "");
- if (r < 0)
- return r;
-
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
return r;
@@ -895,9 +876,9 @@ int bus_map_all_properties(sd_bus *bus,

v = (uint8_t *)userdata + prop->offset;
if (map[i].set)
- r = prop->set(bus, member, m, &error, v);
+ r = prop->set(m->bus, member, m, &error, v);
else
- r = map_basic(bus, member, m, &error, v);
+ r = map_basic(m->bus, member, m, &error, v);

r = sd_bus_message_exit_container(m);
if (r < 0)
@@ -913,7 +894,63 @@ int bus_map_all_properties(sd_bus *bus,
return r;
}

- return r;
+ return sd_bus_message_exit_container(m);
+}
+
+int bus_map_all_properties(sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const struct bus_properties_map *map,
+ void *userdata) {
+ _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(destination);
+ assert(path);
+ assert(map);
+
+ r = sd_bus_call_method( bus,
+ destination,
+ path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ &error,
+ &m,
+ "s", "");
+ if (r < 0)
+ return r;
+
+ return bus_message_map_all_properties(m, map, userdata);
+}
+
+int bus_message_map_properties_changed(sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata) {
+ const char *member;
+ int r, invalidated, i;
+
+ /* skip interface, but allow callers to do that themselves */
+ sd_bus_message_skip(m, "s");
+
+ r = bus_message_map_all_properties(m, map, userdata);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s");
+ if (r < 0)
+ return r;
+
+ invalidated = 0;
+ while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0)
+ for (i = 0; map[i].member; i++)
+ if (streq(map[i].member, member)) {
+ ++invalidated;
+ break;
+ }
+
+ return invalidated;
}

int bus_open_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) {
diff --git a/src/libsystemd-bus/bus-util.h b/src/libsystemd-bus/bus-util.h
index 20739a9..1c6be31 100644
--- a/src/libsystemd-bus/bus-util.h
+++ b/src/libsystemd-bus/bus-util.h
@@ -46,6 +46,12 @@ struct bus_properties_map {

int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);

+int bus_message_map_all_properties(sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata);
+int bus_message_map_properties_changed(sd_bus_message *m,
+ const struct bus_properties_map *map,
+ void *userdata);
int bus_map_all_properties(sd_bus *bus,
const char *destination,
const char *path,
--
1.8.4.2
Lennart Poettering
2013-11-27 21:58:16 UTC
Permalink
Post by David Herrmann
+ /* skip interface, but allow callers to do that themselves */
+ sd_bus_message_skip(m, "s");
This feels a bit like taping over bugs. I'd suggest adding an additional
parameter "const char **interface" to the function which if non-NULL is
updated with the interface name. If it is NULL we'd just skip th
interface as above...

if (interface)
r = sd_bus_message_read("s", interface);
else
r = sd_bus_message_skip(m, "s");

...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:21:36 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 10:58 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+ /* skip interface, but allow callers to do that themselves */
+ sd_bus_message_skip(m, "s");
This feels a bit like taping over bugs. I'd suggest adding an additional
parameter "const char **interface" to the function which if non-NULL is
updated with the interface name. If it is NULL we'd just skip th
interface as above...
if (interface)
r = sd_bus_message_read("s", interface);
else
r = sd_bus_message_skip(m, "s");
That doesn't work. The caller wants "interface" before calling this
helper. You usually pass a different map-array depending on the
interface, right?

I can just require the caller to do the skip/read themselves?

Thanks
David
Lennart Poettering
2013-12-11 00:40:09 UTC
Permalink
Post by David Herrmann
Hi
On Wed, Nov 27, 2013 at 10:58 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+ /* skip interface, but allow callers to do that themselves */
+ sd_bus_message_skip(m, "s");
This feels a bit like taping over bugs. I'd suggest adding an additional
parameter "const char **interface" to the function which if non-NULL is
updated with the interface name. If it is NULL we'd just skip th
interface as above...
if (interface)
r = sd_bus_message_read("s", interface);
else
r = sd_bus_message_skip(m, "s");
That doesn't work. The caller wants "interface" before calling this
helper. You usually pass a different map-array depending on the
interface, right?
I can just require the caller to do the skip/read themselves?
Yeah, sounds better.

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2014-08-28 13:27:24 UTC
Permalink
Hi

On Wed, Dec 11, 2013 at 1:40 AM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
Hi
On Wed, Nov 27, 2013 at 10:58 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+ /* skip interface, but allow callers to do that themselves */
+ sd_bus_message_skip(m, "s");
This feels a bit like taping over bugs. I'd suggest adding an additional
parameter "const char **interface" to the function which if non-NULL is
updated with the interface name. If it is NULL we'd just skip th
interface as above...
if (interface)
r = sd_bus_message_read("s", interface);
else
r = sd_bus_message_skip(m, "s");
That doesn't work. The caller wants "interface" before calling this
helper. You usually pass a different map-array depending on the
interface, right?
I can just require the caller to do the skip/read themselves?
Yeah, sounds better.
Digging out old threads: *fixed and pushed*

Thanks
David

David Herrmann
2013-11-27 18:48:39 UTC
Permalink
In several situations we want to link a binary file into our executable
and access it from our C code. The easiest way is to transform it into a
C-array and compile it as usual. However, for large files (>1MB) such
compilations can take a considerable amount of time or even fail on
low-memory systems.

This adds a new automake-target to link binary sources directly. Instead
of transforming it into a C-array, we simply use "ld -r" to create an
object file via:
ld -r -o my-source.bin.o --format=binary my-source.bin
We also use "-z noexecstack" to mark "my-source.bin.o" to not require an
executable stack.

As we only want to support read-only data sources here, we do some
post-processing to mark the object as read-only via:
objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents my-source.bin.o

As libtool requires "*.lo" files, we cannot link this object-file
directly. Thus, we also create a fake "*.lo" file for such objects which
libtool can use. Note that libtool actually *requires* the comment-section
in "*.lo" files (ugh?!) so we need to fake that, too.

How to use this helper?
- put your binary source file into the tree as:
src/somewhere/something.bin
- for the library you want to link that to, add this to mylib_LIBADD:
src/somewhere/something.bin.lo
This causes the helper to create src/somewhere/something.bin.[o,lo] and
it will be linked as a normal object file.

GNU-ld automatically creates 3 symbols for such objects, but the important
symbols are:
extern const char _binary_src_somewhere_something_bin_start[];
extern const char _binary_src_somewhere_something_bin_end[];
Use these to access start/end of the binary section.

I tested this with in-tree and out-of-tree builds, with GNU-ld and
GNU-gold and cross-compilation. All worked fine..
---
Makefile.am | 19 +++++++++++++++++++
configure.ac | 5 +++++
2 files changed, 24 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index 4aa2bdf..ce27e82 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -632,6 +632,25 @@ EXTRA_DIST += \
xml_helper.py

# ------------------------------------------------------------------------------
+CLEANFILES += *.bin.lo *.bin.o
+
+%.bin.lo: %.bin
+ $(AM_V_GEN)$(LD) -r -o "$*.bin.o" -z noexecstack --format=binary "$<"
+ $(AM_V_at)$(OBJCOPY) --rename-section .data=.rodata,alloc,load,readonly,data,contents "$*.bin.o"
+ $(AM_V_at)echo "# $@ - a libtool object file" >"$@"
+ $(AM_V_at)echo "# Generated by $(shell $(LIBTOOL) --version | head -n 1)" >>"$@"
+ $(AM_V_at)echo "#" >>"$@"
+ $(AM_V_at)echo "# Please DO NOT delete this file!" >>"$@"
+ $(AM_V_at)echo "# It is necessary for linking the library." >>"$@"
+ $(AM_V_at)echo >>"$@"
+ $(AM_V_at)echo "# Name of the PIC object." >>"$@"
+ $(AM_V_at)echo "pic_object='$(notdir $*).bin.o'" >>"$@"
+ $(AM_V_at)echo >>"$@"
+ $(AM_V_at)echo "# Name of the non-PIC object" >>"$@"
+ $(AM_V_at)echo "non_pic_object='$(notdir $*).bin.o'" >>"$@"
+ $(AM_V_at)echo >>"$@"
+
+# ------------------------------------------------------------------------------
noinst_LTLIBRARIES += \
libsystemd-rtnl.la

diff --git a/configure.ac b/configure.ac
index c0656f4..3fd05da 100644
--- a/configure.ac
+++ b/configure.ac
@@ -101,6 +101,11 @@ if test -z "$GPERF" ; then
AC_MSG_ERROR([*** gperf not found])
fi

+AC_CHECK_TOOL([OBJCOPY], objcopy)
+if test -z "$OBJCOPY" ; then
+ AC_MSG_ERROR([*** objcopy not found])
+fi
+
# ------------------------------------------------------------------------------
address_sanitizer_cflags=
address_sanitizer_cppflags=
--
1.8.4.2
Greg KH
2013-11-27 21:31:18 UTC
Permalink
Post by David Herrmann
In several situations we want to link a binary file into our executable
and access it from our C code. The easiest way is to transform it into a
C-array and compile it as usual. However, for large files (>1MB) such
compilations can take a considerable amount of time or even fail on
low-memory systems.
This adds a new automake-target to link binary sources directly. Instead
of transforming it into a C-array, we simply use "ld -r" to create an
ld -r -o my-source.bin.o --format=binary my-source.bin
We also use "-z noexecstack" to mark "my-source.bin.o" to not require an
executable stack.
As we only want to support read-only data sources here, we do some
objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents my-source.bin.o
As libtool requires "*.lo" files, we cannot link this object-file
directly. Thus, we also create a fake "*.lo" file for such objects which
libtool can use. Note that libtool actually *requires* the comment-section
in "*.lo" files (ugh?!) so we need to fake that, too.
How to use this helper?
src/somewhere/something.bin
src/somewhere/something.bin.lo
This causes the helper to create src/somewhere/something.bin.[o,lo] and
it will be linked as a normal object file.
GNU-ld automatically creates 3 symbols for such objects, but the important
extern const char _binary_src_somewhere_something_bin_start[];
extern const char _binary_src_somewhere_something_bin_end[];
Use these to access start/end of the binary section.
I tested this with in-tree and out-of-tree builds, with GNU-ld and
GNU-gold and cross-compilation. All worked fine..
That's crazy, very nice job in doing this :)

I tried to do this a while ago, and gave up and just used a perl script
and a .c file with a big array, like 'xxd -i' can create. I think I'll
"steal" this idea for other projects if you don't mind, as it makes
things easier just to use the linker.

greg k-h
David Herrmann
2013-11-28 06:47:21 UTC
Permalink
Hi
Post by Greg KH
Post by David Herrmann
In several situations we want to link a binary file into our executable
and access it from our C code. The easiest way is to transform it into a
C-array and compile it as usual. However, for large files (>1MB) such
compilations can take a considerable amount of time or even fail on
low-memory systems.
This adds a new automake-target to link binary sources directly. Instead
of transforming it into a C-array, we simply use "ld -r" to create an
ld -r -o my-source.bin.o --format=binary my-source.bin
We also use "-z noexecstack" to mark "my-source.bin.o" to not require an
executable stack.
As we only want to support read-only data sources here, we do some
objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents my-source.bin.o
As libtool requires "*.lo" files, we cannot link this object-file
directly. Thus, we also create a fake "*.lo" file for such objects which
libtool can use. Note that libtool actually *requires* the comment-section
in "*.lo" files (ugh?!) so we need to fake that, too.
How to use this helper?
src/somewhere/something.bin
src/somewhere/something.bin.lo
This causes the helper to create src/somewhere/something.bin.[o,lo] and
it will be linked as a normal object file.
GNU-ld automatically creates 3 symbols for such objects, but the important
extern const char _binary_src_somewhere_something_bin_start[];
extern const char _binary_src_somewhere_something_bin_end[];
Use these to access start/end of the binary section.
I tested this with in-tree and out-of-tree builds, with GNU-ld and
GNU-gold and cross-compilation. All worked fine..
That's crazy, very nice job in doing this :)
I tried to do this a while ago, and gave up and just used a perl script
and a .c file with a big array, like 'xxd -i' can create. I think I'll
"steal" this idea for other projects if you don't mind, as it makes
things easier just to use the linker.
I figured that out about 1 year ago, took me way too long to get working.
Please, go ahead and copy it! And if any issue comes up, let me know.

Thanks
David
David Herrmann
2013-11-27 18:48:40 UTC
Permalink
If we want to interact with a user in the initrd, during
emergency-situations, in single-user mode, or in any other rather limited
situation, we currently rely on the kernel to do input and graphics access
for us. More precisely, we rely on the VT layer to do keyboard parsing and
font rendering. Or in other words: The *kernel* provides our UI!

This is bad for the same reasons we don't put any other UI into the
kernel. However, there are a few reasons to keep a minimal UI in the
kernel:
1) show panic-screen with kernel oops message
2) show log-screen during early boot
3) allow kernel-debugging via kdb
While people using kdb are encouraged to keep VTs, for 1) and 2) there is
a replacement via fblog/drmlog.

So we run out of reasons to keep a UI in the kernel. However, to allow
moving the UI handling to userspace, we need a bunch of helpers:
- keyboard handling: convert keycodes into keysyms and modifiers/..
- modesetting: program the gfx pipeline and provide a rendering
infrastructure
- fonts: to render text, we need some basic fonts
- hotplugging: device detection and assignment during runtime
(Note that all these are implemented (often quite rudimentary) in the
kernel to allow a basic UI.)

This patch introduces sd-gfx, a systemd-internal library dealing with all
these things. Note that it is designed to be exported some day, but for
now we keep it internal.
While the library itself will be kept small and almost self-contained, it
is designed to be extensible and can be used in rather complex graphics
applications. For systemd, we plan to add a basic emergency-console, an
initrd password-query, kernel-log-screen and a fallback login-screen.
These allow booting without CONFIG_VT and provide a basic system for
seats without VTs (either CONFIT_VT=n or seats != seat0).

As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont. It contains glyphs
for the *whole* Base-Multilingual-Plane of Unicode and thus allows
internationalized text.

The "make-unifont.py" script is used by the "make update-unifont" custom
target to regenerate the unifont files. As this can take quite some time,
we check the result into git so only maintainers need to run this.

The binary file contains all glyphs in a compressed 1-bit-per-pixel format
for all 8x16 and 16x16 glyphs. It is linked directly into libsystemd-gfx
via the *.bin makefile target. Binary size is 2.1MB, but thanks to paging,
the kernel only loads required pages into memory. A ASCII-only screen thus
only needs 40k VIRT mem.
---
Hi

I removed the unifont-data from this patch so this will not apply cleanly.
However, the ML would reject the huge patch otherwise. If anyone is interested
in the raw patch, the series is available on:
http://cgit.freedesktop.org/~dvdhrm/systemd/log/?h=console

Thanks
David

Makefile.am | 31 +
configure.ac | 10 +
make-unifont.py | 138 +
src/libsystemd-gfx/.gitignore | 1 +
src/libsystemd-gfx/Makefile | 1 +
src/libsystemd-gfx/gfx-unifont.c | 273 +
src/libsystemd-gfx/unifont.bin | Bin 0 -> 2162688 bytes
src/libsystemd-gfx/unifont.hex | 63488 +++++++++++++++++++++++++++++++++++++
src/systemd/sd-gfx.h | 65 +
9 files changed, 64007 insertions(+)
create mode 100755 make-unifont.py
create mode 100644 src/libsystemd-gfx/.gitignore
create mode 120000 src/libsystemd-gfx/Makefile
create mode 100644 src/libsystemd-gfx/gfx-unifont.c
create mode 100644 src/libsystemd-gfx/unifont.bin
create mode 100644 src/libsystemd-gfx/unifont.hex
create mode 100644 src/systemd/sd-gfx.h

diff --git a/Makefile.am b/Makefile.am
index ce27e82..2edb091 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3853,6 +3853,37 @@ EXTRA_DIST += \
endif

# ------------------------------------------------------------------------------
+if HAVE_GFX
+noinst_LTLIBRARIES += \
+ libsystemd-gfx.la
+
+libsystemd_gfx_la_SOURCES = \
+ src/libsystemd-gfx/sd-gfx.h \
+ src/libsystemd-gfx/gfx-unifont.c
+
+libsystemd_gfx_la_CFLAGS = \
+ $(AM_CFLAGS)
+
+libsystemd_gfx_la_LIBADD = \
+ libsystemd-shared.la \
+ src/libsystemd-gfx/unifont.bin.lo
+
+src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
+ $(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.hex | $(PYTHON) $< >$@
+
+src/libsystemd-gfx/unifont.cmp: make-unifont.py src/libsystemd-gfx/unifont.bin
+ $(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.bin | $(PYTHON) $< verify >$@
+
+update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp
+ @RET=`diff -u src/libsystemd-gfx/unifont.hex src/libsystemd-gfx/unifont.cmp | wc -l` ; \
+ if test "x$$?" != "x0" -o "x$$RET" != "x0" ; then \
+ echo "Generated Unifont-file differs from original; generator probably broken" ; \
+ exit 1 ; \
+ fi
+ @echo "unifont.bin has been regenerated"
+endif
+
+# ------------------------------------------------------------------------------
if ENABLE_NETWORKD
rootlibexec_PROGRAMS += \
systemd-networkd
diff --git a/configure.ac b/configure.ac
index 3fd05da..354673a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -291,6 +291,15 @@ fi
AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])

# ------------------------------------------------------------------------------
+have_gfx=no
+AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
+if test "x$enable_gfx" != "xno"; then
+ AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built])
+ have_gfx=yes
+fi
+AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
+
+# ------------------------------------------------------------------------------
have_blkid=no
AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support]))
if test "x$enable_blkid" != "xno"; then
@@ -1079,6 +1088,7 @@ AC_MSG_RESULT([
polkit: ${have_polkit}
efi: ${have_efi}
kmod: ${have_kmod}
+ sd-gfx: ${have_gfx}
blkid: ${have_blkid}
nss-myhostname: ${have_myhostname}
gudev: ${enable_gudev}
diff --git a/make-unifont.py b/make-unifont.py
new file mode 100755
index 0000000..375c8a4
--- /dev/null
+++ b/make-unifont.py
@@ -0,0 +1,138 @@
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+# This file is part of systemd.
+#
+# Copyright 2013 David Herrmann <***@gmail.com>
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Parse a unifont.hex file and produce a compressed binary-format we use in
+# our gfx applications. Can also do the reverse to verify correctness.
+#
+
+from __future__ import print_function
+import re
+import sys
+import fileinput
+import struct
+
+#
+# Write "bits" array as binary output.
+#
+
+def write_bin_entry(entry):
+ l = len(entry)
+ if l != 32 and l != 64:
+ entry = "0" * 64
+ l = 0
+ elif l < 64:
+ entry += "0" * (64 - l)
+
+ sys.stdout.buffer.write(struct.pack('B', int(l / 32)))
+
+ for i in range(0, 64, 2):
+ c = int(entry[i:i+2], 16)
+ sys.stdout.buffer.write(struct.pack('B', c))
+
+def write_bin(bits):
+ for idx in range(len(bits)):
+ write_bin_entry(bits[idx])
+
+#
+# Parse hex file into "bits" array
+#
+
+def parse_hex_line(bits, line):
+ m = re.match(r"^([0-9A-Fa-f]+):([0-9A-Fa-f]+)$", line)
+ if m == None:
+ return
+
+ idx = int(m.group(1), 16)
+ val = m.group(2)
+
+ # insert skipped lines
+ for i in range(len(bits), idx):
+ bits.append("")
+
+ bits.insert(idx, val)
+
+def parse_hex():
+ bits = []
+
+ for line in sys.stdin:
+ if not line:
+ continue
+ if line.startswith("#"):
+ continue
+
+ parse_hex_line(bits, line)
+
+ return bits
+
+#
+# Write "bits" array as text-file line-by-line to stdout.
+#
+
+def write_hex_line(idx, entry):
+ if entry:
+ print("%04X:%s" % (idx, entry))
+
+def write_hex(bits):
+ for idx, entry in enumerate(bits):
+ if not entry:
+ continue
+
+ write_hex_line(idx, entry)
+
+#
+# Parse a binary file into "bits" so we can verify the correctness of a given
+# binary file.
+#
+
+def parse_bin_entry(bits, chunk):
+ entry = ""
+ l = struct.unpack('B', chunk[0:1])[0]
+ for c in chunk[1:l*16+1]:
+ entry += format(c, "02X")
+
+ bits.append(entry)
+
+def parse_bin():
+ bits = []
+
+ while True:
+ chunk = sys.stdin.buffer.read(33)
+ if chunk:
+ parse_bin_entry(bits, chunk)
+ else:
+ break
+
+ return bits
+
+#
+# In normal mode we simply read line by line from standard-input (or first
+# argument) and write the binary-file to standard-output.
+#
+# In verify-mode, we read the binary-file from standard-input and write the
+# text-file line-by-line to standard-output.
+#
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1 and sys.argv[1] == "verify":
+ bits = parse_bin()
+ write_hex(bits)
+ else:
+ bits = parse_hex()
+ write_bin(bits)
diff --git a/src/libsystemd-gfx/.gitignore b/src/libsystemd-gfx/.gitignore
new file mode 100644
index 0000000..a792d8f
--- /dev/null
+++ b/src/libsystemd-gfx/.gitignore
@@ -0,0 +1 @@
+/unifont.cmp
diff --git a/src/libsystemd-gfx/Makefile b/src/libsystemd-gfx/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/libsystemd-gfx/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/libsystemd-gfx/gfx-unifont.c b/src/libsystemd-gfx/gfx-unifont.c
new file mode 100644
index 0000000..c2fc879
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-unifont.c
@@ -0,0 +1,273 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "def.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+/* Glyphs are linked as binary data. The data layout is a size-byte followed by
+ * 32 data bytes. The data bytes are padded with 0 if the size is smaller than
+ * 32. The size-byte specifies the size of a single line. Each glyph always
+ * consists of 16 lines.
+ * Currently, size==1 is used for single-width glyphs and size==2 for
+ * double-width glyphs. */
+
+struct unifont_data {
+ uint8_t cells;
+ uint8_t data[32];
+} _packed_;
+
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_start[];
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_end[];
+static const unsigned int unifont_width = 8;
+static const unsigned int unifont_stride = 1;
+static const unsigned int unifont_max_cells = 2;
+static const unsigned int unifont_height = 16;
+
+static const struct unifont_data unifont_fallback = {
+ .cells = 1,
+ .data = "\x00\x00\x00\x7E\x66\x5A\x5A\x7A\x76\x76\x7E\x76\x76\x7E\x00\x00",
+};
+
+static const struct unifont_data *unifont_get(uint32_t ucs4) {
+ const struct unifont_data *begin, *end, *g;
+
+ begin = _binary_src_libsystemd_gfx_unifont_bin_start;
+ end = _binary_src_libsystemd_gfx_unifont_bin_end;
+
+ g = &begin[ucs4];
+ if (g >= end)
+ return &unifont_fallback;
+ if (g->cells == 0 || g->cells > unifont_max_cells)
+ return &unifont_fallback;
+
+ return g;
+}
+
+/*
+ * Fonts
+ * We provide a built-in static font. This can be used to render text in any
+ * situation without depending on huge text-pipelines like pango. The font is
+ * based on "GNU Unifont" which provides fixed-size glyphs for the whole
+ * Unicode Basic Multilingual Plane.
+ * This can be used in initrds, emergency-consoles and other situations where a
+ * full text-pipeline would be overkill.
+ *
+ * Note that we provide some enhanced features to properly support all systems.
+ * This currently includes:
+ * - scaling: You can specify a "ppi" value to scale the static fonts. This
+ * can only be done with integer-scaling and should only be used
+ * as reading-aid or for high-DPI screens.
+ * - combining: Glyph-combining (eg., for unicode combining characters) is
+ * implemented in the renderer to correctly draw combined
+ * characters for non-latin scripts. Character-combining is left
+ * to the caller, we only allow drawing a set of characters as a
+ * single glyph.
+ * - caching: To support fast lookups we cache modified glyphs.
+ */
+
+typedef struct gfx_glyph gfx_glyph;
+
+struct gfx_glyph {
+ sd_gfx_buffer buf;
+};
+
+struct sd_gfx_font {
+ unsigned int ppi;
+ unsigned int width;
+ unsigned int height;
+ Hashmap *glyphs;
+
+ gfx_glyph fallback;
+};
+
+static void gfx_glyph_blend(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ unsigned int i, j;
+ const uint8_t *src;
+ uint8_t *dst;
+
+ /*
+ * TODO: scale glyph according to @ppi
+ */
+
+ src = u->data;
+ dst = g->buf.data;
+
+ for (i = 0; i < unifont_height; ++i) {
+ for (j = 0; j < u->cells; ++j)
+ dst[j] |= src[j];
+
+ src += u->cells * unifont_stride;
+ dst += g->buf.stride;
+ }
+}
+
+static int gfx_glyph_render(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ g->buf.width = u->cells * unifont_width;
+ g->buf.height = unifont_height;
+ g->buf.stride = u->cells * unifont_stride;
+ g->buf.format = SD_GFX_BUFFER_FORMAT_A1;
+
+ g->buf.data = calloc(unifont_height, unifont_max_cells * unifont_stride);
+ if (!g->buf.data)
+ return -ENOMEM;
+
+ gfx_glyph_blend(g, ppi, u);
+ return 0;
+}
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi) {
+ sd_gfx_font *font;
+ int r;
+
+ ppi = CLAMP(ppi, 10U, 1000U);
+
+ font = calloc(1, sizeof(*font));
+ if (!font)
+ return log_oom();
+
+ font->ppi = ppi;
+ font->width = unifont_width;
+ font->height = unifont_height;
+
+ font->glyphs = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!font->glyphs) {
+ free(font);
+ return log_oom();
+ }
+
+ r = gfx_glyph_render(&font->fallback, font->ppi, &unifont_fallback);
+ if (r < 0) {
+ hashmap_free(font->glyphs);
+ free(font);
+ return log_oom();
+ }
+
+ *out = font;
+ return 0;
+}
+
+static void gfx_glyph_free(gfx_glyph *g) {
+ free(g->buf.data);
+ free(g);
+}
+
+void sd_gfx_font_free(sd_gfx_font *font) {
+ gfx_glyph *g;
+
+ if (!font)
+ return;
+
+ while ((g = hashmap_steal_first(font->glyphs)))
+ gfx_glyph_free(g);
+
+ hashmap_free(font->glyphs);
+ free(font);
+}
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font) {
+ return font->ppi;
+}
+
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font) {
+ return font->width;
+}
+
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font) {
+ return font->height;
+}
+
+/*
+ * Render a glyph
+ * This first tries to look up the glyph in the cache. If found, it is returned,
+ * otherwise the glyph is rendered and put into the cache.
+ *
+ * This function never fails. If the glyph cannot be rendered, a fallback glyph
+ * is returned. Note that this function can render multiple glyphs into a single
+ * buffer. You simply have to pass multiple codepoints via @ucs4 / @len.
+ * However, this should only be used for combining-characters.
+ *
+ * Whenever you render a glyph, this functions needs a unique ID to identify it.
+ * You have to pass it via @id. If you render only a single codepoint, simply
+ * pass the codepoint as @id (or pass 0 in which case the first codepoint is
+ * automatically taken as id). But if you render combined codepoints, you need
+ * to allocate a unique ID for them. See libtsm for an example how to do that.
+ *
+ * The lifetime of the returned buffer is bound to @font. You don't have to free
+ * it yourself.
+ */
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out) {
+ const struct unifont_data *u;
+ gfx_glyph *g;
+
+ if (!len)
+ goto error;
+
+ if (!id)
+ id = *ucs4;
+
+ g = hashmap_get(font->glyphs, INT_TO_PTR(id));
+ if (g)
+ goto out;
+
+ g = calloc(1, sizeof(*g));
+ if (!g) {
+ log_oom();
+ goto error;
+ }
+
+ u = unifont_get(*ucs4);
+ if (gfx_glyph_render(g, font->ppi, u) < 0) {
+ log_oom();
+ free(g);
+ goto error;
+ }
+
+ while (--len) {
+ u = unifont_get(*++ucs4);
+ gfx_glyph_blend(g, font->ppi, u);
+ }
+
+ if (hashmap_put(font->glyphs, INT_TO_PTR(id), g) < 0) {
+ log_oom();
+ free(g->buf.data);
+ free(g);
+ goto error;
+ }
+
+ goto out;
+
+error:
+ g = &font->fallback;
+out:
+ *out = &g->buf;
+}
diff --git a/src/libsystemd-gfx/unifont.bin b/src/libsystemd-gfx/unifont.bin
new file mode 100644
index 0000000000000000000000000000000000000000..0b32d80222dd465d0612188915ef67355dc73e4d
GIT binary patch
literal 2162688
z9F*YJIJ$6q>;c^VB)T=i&<gm^zxN+SppMS|-OpAY!~fZzt8D^U-P?cj(n~*i<Be_n

[... skipped for ML ...]

h<$hpdYOEeTz{h7Yzz4==k?}x2v&LpIyNUqW`UBL12zCGf

literal 0
HcmV?d00001

diff --git a/src/libsystemd-gfx/unifont.hex b/src/libsystemd-gfx/unifont.hex
new file mode 100644
index 0000000..90672a8
--- /dev/null
+++ b/src/libsystemd-gfx/unifont.hex
@@ -0,0 +1,63488 @@
+0000:AAAA00018000000180004A51EA505A51C99E0001800000018000000180005555
+0001:AAAA00018000000180003993C252325F8A527193800000018000000180005555

[... skipped for ML ...]

+FFFE:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEF87FFFFFFFF
+FFFF:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFF
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
new file mode 100644
index 0000000..c46fbd4
--- /dev/null
+++ b/src/systemd/sd-gfx.h
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdgfxhfoo
+#define foosdgfxhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <systemd/_sd-common.h>
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_gfx_buffer sd_gfx_buffer;
+typedef struct sd_gfx_font sd_gfx_font;
+
+/* memory buffer */
+
+enum {
+ SD_GFX_BUFFER_FORMAT_A1,
+ SD_GFX_BUFFER_FORMAT_A8,
+ SD_GFX_BUFFER_FORMAT_XRGB8888,
+};
+
+struct sd_gfx_buffer {
+ unsigned int width;
+ unsigned int height;
+ int stride;
+ unsigned int format;
+ void *data;
+};
+
+/* unifont */
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi);
+void sd_gfx_font_free(sd_gfx_font *font);
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font);
+
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);
+
+_SD_END_DECLARATIONS;
+
+#endif
--
1.8.4.2
Lennart Poettering
2013-11-27 22:11:29 UTC
Permalink
Post by David Herrmann
This patch introduces sd-gfx, a systemd-internal library dealing with all
these things. Note that it is designed to be exported some day, but for
now we keep it internal.
While the library itself will be kept small and almost self-contained, it
is designed to be extensible and can be used in rather complex graphics
applications. For systemd, we plan to add a basic emergency-console, an
initrd password-query, kernel-log-screen and a fallback login-screen.
These allow booting without CONFIG_VT and provide a basic system for
seats without VTs (either CONFIT_VT=n or seats != seat0).
BTW, one more use of this I'd like to see: if we boot-up and detect that
no AC and very low battery we should show a pretty screen and block the
boot until AC is plugged in or the user hits some magic key...
Post by David Herrmann
+static int gfx_glyph_render(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ g->buf.width = u->cells * unifont_width;
+ g->buf.height = unifont_height;
+ g->buf.stride = u->cells * unifont_stride;
+ g->buf.format = SD_GFX_BUFFER_FORMAT_A1;
+
+ g->buf.data = calloc(unifont_height, unifont_max_cells * unifont_stride);
+ if (!g->buf.data)
+ return -ENOMEM;
Nice, this must be the first use of alloc() I have ever seen that
actually takes benefit of the weird parameters it takes ;-)
Post by David Herrmann
+
+ gfx_glyph_blend(g, ppi, u);
+ return 0;
+}
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi) {
Hmm, so far we followed the rough rule that parameters we pass out are
listed last on the function parameter list.

int sd_gfx_font_new(unsigned ppi, sd_gfx_fond **out);
Post by David Herrmann
+ sd_gfx_font *font;
+ int r;
+
+ ppi = CLAMP(ppi, 10U, 1000U);
+
+ font = calloc(1, sizeof(*font));
font = new0(sd_gfx_font, 1);
Post by David Herrmann
+ font->glyphs = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!font->glyphs) {
+ free(font);
+ return log_oom();
Hmm, "library" code should never log. Please just return -ENOMEM
here. log_oom() should be used in main programs only really...
Post by David Herrmann
+void sd_gfx_font_free(sd_gfx_font *font) {
+ gfx_glyph *g;
For public APIs we are pretty defensive and check all parameters we
take. Given you kinda made this a public library it might make sense to
follow that rule here, too. assert_return() is particularly useful for
this. (Well, not for the instance above, bug if you return a negative
errno it is super-useful).

Most destructors we have tend to accept NULL pointers too. It makes
clean-up code a lot easier (especially in combination with gcc cleanup
attributes). libc free() takes NULL pointers too, so this is a natural
extension.
Post by David Herrmann
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out) {
+ const struct unifont_data *u;
+ gfx_glyph *g;
+
+ if (!len)
+ goto error;
+
+ if (!id)
+ id = *ucs4;
+
+ g = hashmap_get(font->glyphs, INT_TO_PTR(id));
+ if (g)
+ goto out;
+
+ g = calloc(1, sizeof(*g));
g = new(gfx_glyph, 1);
Post by David Herrmann
+ if (!g) {
+ log_oom();
+ goto error;
+ }
+
+ u = unifont_get(*ucs4);
+ if (gfx_glyph_render(g, font->ppi, u) < 0) {
+ log_oom();
+ free(g);
+ goto error;
+ }
+
+ while (--len) {
+ u = unifont_get(*++ucs4);
+ gfx_glyph_blend(g, font->ppi, u);
+ }
+
+ if (hashmap_put(font->glyphs, INT_TO_PTR(id), g) < 0) {
+ log_oom();
+ free(g->buf.data);
+ free(g);
+ goto error;
+ }
+
+ goto out;
+
+ g = &font->fallback;
+ *out = &g->buf;
+}
Hmm, we so far followed to rule to not clobber return parameters on
failure. i.e. we try hard to fill any return values in until we know
that nothing can fail anymore. This makes the contract for the caller
much easier who can refrain from freeing/resetting any parameters if a
call failed...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:32:08 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:11 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
This patch introduces sd-gfx, a systemd-internal library dealing with all
these things. Note that it is designed to be exported some day, but for
now we keep it internal.
While the library itself will be kept small and almost self-contained, it
is designed to be extensible and can be used in rather complex graphics
applications. For systemd, we plan to add a basic emergency-console, an
initrd password-query, kernel-log-screen and a fallback login-screen.
These allow booting without CONFIG_VT and provide a basic system for
seats without VTs (either CONFIT_VT=n or seats != seat0).
BTW, one more use of this I'd like to see: if we boot-up and detect that
no AC and very low battery we should show a pretty screen and block the
boot until AC is plugged in or the user hits some magic key...
Yepp, sounds good. Would be like 200 lines to write that, I guess.
Post by Lennart Poettering
Post by David Herrmann
+static int gfx_glyph_render(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ g->buf.width = u->cells * unifont_width;
+ g->buf.height = unifont_height;
+ g->buf.stride = u->cells * unifont_stride;
+ g->buf.format = SD_GFX_BUFFER_FORMAT_A1;
+
+ g->buf.data = calloc(unifont_height, unifont_max_cells * unifont_stride);
+ if (!g->buf.data)
+ return -ENOMEM;
Nice, this must be the first use of alloc() I have ever seen that
actually takes benefit of the weird parameters it takes ;-)
Do I get an award or something? :)
Post by Lennart Poettering
Post by David Herrmann
+
+ gfx_glyph_blend(g, ppi, u);
+ return 0;
+}
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi) {
Hmm, so far we followed the rough rule that parameters we pass out are
listed last on the function parameter list.
int sd_gfx_font_new(unsigned ppi, sd_gfx_fond **out);
Post by David Herrmann
+ sd_gfx_font *font;
+ int r;
+
+ ppi = CLAMP(ppi, 10U, 1000U);
+
+ font = calloc(1, sizeof(*font));
font = new0(sd_gfx_font, 1);
I actually dislike new0() to require the type. I'd prefer:
font = new0(font);
But this is horrible to read, so I went with calloc(1, sizeof(*xyz));
But the systemd code-base seems to use new0() consistently so I can
adjust all my calloc()s if you want? I'm fine with keeping consistency
here.
Post by Lennart Poettering
Post by David Herrmann
+ font->glyphs = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!font->glyphs) {
+ free(font);
+ return log_oom();
Hmm, "library" code should never log. Please just return -ENOMEM
here. log_oom() should be used in main programs only really...
Post by David Herrmann
+void sd_gfx_font_free(sd_gfx_font *font) {
+ gfx_glyph *g;
For public APIs we are pretty defensive and check all parameters we
take. Given you kinda made this a public library it might make sense to
follow that rule here, too. assert_return() is particularly useful for
this. (Well, not for the instance above, bug if you return a negative
errno it is super-useful).
I wanna avoid these in fast-paths (the blending/blitting functions)
but for all the other ones, I can add assert()s.
Post by Lennart Poettering
Most destructors we have tend to accept NULL pointers too. It makes
clean-up code a lot easier (especially in combination with gcc cleanup
attributes). libc free() takes NULL pointers too, so this is a natural
extension.
Oh, I know. This is presumably the only destructor where I somehow
forgot that. Fixed.
Post by Lennart Poettering
Post by David Herrmann
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out) {
+ const struct unifont_data *u;
+ gfx_glyph *g;
+
+ if (!len)
+ goto error;
+
+ if (!id)
+ id = *ucs4;
+
+ g = hashmap_get(font->glyphs, INT_TO_PTR(id));
+ if (g)
+ goto out;
+
+ g = calloc(1, sizeof(*g));
g = new(gfx_glyph, 1);
Post by David Herrmann
+ if (!g) {
+ log_oom();
+ goto error;
+ }
+
+ u = unifont_get(*ucs4);
+ if (gfx_glyph_render(g, font->ppi, u) < 0) {
+ log_oom();
+ free(g);
+ goto error;
+ }
+
+ while (--len) {
+ u = unifont_get(*++ucs4);
+ gfx_glyph_blend(g, font->ppi, u);
+ }
+
+ if (hashmap_put(font->glyphs, INT_TO_PTR(id), g) < 0) {
+ log_oom();
+ free(g->buf.data);
+ free(g);
+ goto error;
+ }
+
+ goto out;
+
+ g = &font->fallback;
+ *out = &g->buf;
+}
Hmm, we so far followed to rule to not clobber return parameters on
failure. i.e. we try hard to fill any return values in until we know
that nothing can fail anymore. This makes the contract for the caller
much easier who can refrain from freeing/resetting any parameters if a
call failed...
Oh, I don't clobber the return value here. This is a "void" function,
it never fails. The error-path just stores a fallback-glyph in the
out-buffer. This way, you get some weird
"this-is-an-unprintable-or-caused-by-error"-glyph instead of..
nothing. The application can assume glyph-rendering always works and
just don't deal with possible errors here.

I actually rely quite often on "out" parameters to not get touched at
all if a function fails. It makes error-paths so much nicer.

Thanks
David
Lennart Poettering
2013-12-11 00:43:51 UTC
Permalink
Post by David Herrmann
Post by Lennart Poettering
Post by David Herrmann
+ sd_gfx_font *font;
+ int r;
+
+ ppi = CLAMP(ppi, 10U, 1000U);
+
+ font = calloc(1, sizeof(*font));
font = new0(sd_gfx_font, 1);
font = new0(font);
But this is horrible to read, so I went with calloc(1, sizeof(*xyz));
But the systemd code-base seems to use new0() consistently so I can
adjust all my calloc()s if you want? I'm fine with keeping consistency
here.
I'd really prefer that. Not that new() is a glibc thing, where they have
g_new(). Of all the type-safe macros for allocation I certainly like it
the best...
Post by David Herrmann
Post by Lennart Poettering
For public APIs we are pretty defensive and check all parameters we
take. Given you kinda made this a public library it might make sense to
follow that rule here, too. assert_return() is particularly useful for
this. (Well, not for the instance above, bug if you return a negative
errno it is super-useful).
I wanna avoid these in fast-paths (the blending/blitting functions)
but for all the other ones, I can add assert()s.
Yes, please do not add assert()s in inner loops!

I mean, the whole assert() story is not that definitive either. I add
them to a lot of functions, but I can totally understand when people
think I am adding to many of them....

Lennart
--
Lennart Poettering, Red Hat
Zbigniew Jędrzejewski-Szmek
2013-12-01 05:28:18 UTC
Permalink
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
FontException allows embedding in "documents", so it doesn't apply.
It would be possible have some sources which are GPLv2+ only, but I
think we want to avoid such complications.

Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.

So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.

Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.

Zbyszek

[1] http://packages.debian.org/sid/all/unifont/filelist
Shawn Landden
2013-12-01 06:18:02 UTC
Permalink
On Sat, Nov 30, 2013 at 9:28 PM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
FontException allows embedding in "documents", so it doesn't apply.
It would be possible have some sources which are GPLv2+ only, but I
think we want to avoid such complications.
Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
The font is needed in the initramfs
Post by Zbigniew Jędrzejewski-Szmek
Zbyszek
[1] http://packages.debian.org/sid/all/unifont/filelist
_______________________________________________
systemd-devel mailing list
http://lists.freedesktop.org/mailman/listinfo/systemd-devel
David Herrmann
2013-12-01 09:05:49 UTC
Permalink
Hi

On Sun, Dec 1, 2013 at 6:28 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
FontException allows embedding in "documents", so it doesn't apply.
I disagree. I'm allowed to embed GNU-Unifont in a pdf/postscript file,
right? However, postscript is as turing-complete as x86-assembler, so
I don't see the difference between an ELF-document and a
postscript-document.
Post by Zbigniew Jędrzejewski-Szmek
It would be possible have some sources which are GPLv2+ only, but I
think we want to avoid such complications.
It's not about sources. Assuming the font-exception doesn't apply,
this only means all binaries linking to libsystemd-gfx are GPLv2. The
sources stay LGPL as usual.
Post by Zbigniew Jędrzejewski-Szmek
Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
Post by Zbigniew Jędrzejewski-Szmek
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
That's what I currently do.
Post by Zbigniew Jędrzejewski-Szmek
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
Where is the difference between build-time linking and mmap()?
(regarding licensing)
Also, where's the point of keeping libsystemd-gfx.so LGPL just to have
a *mandatory* dependency which is GPL?

Thanks
David
Zbigniew Jędrzejewski-Szmek
2013-12-01 14:32:14 UTC
Permalink
Post by David Herrmann
Hi
On Sun, Dec 1, 2013 at 6:28 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
FontException allows embedding in "documents", so it doesn't apply.
I disagree. I'm allowed to embed GNU-Unifont in a pdf/postscript file,
right? However, postscript is as turing-complete as x86-assembler, so
I don't see the difference between an ELF-document and a
postscript-document.
I don't think you can convincigly argue that either systemd-208.tar.gz
or systemd-gfx are "documents". The *intent* of the FontException is pretty
clear, and embedding in arbitrary programs is not it.
Post by David Herrmann
Post by Zbigniew Jędrzejewski-Szmek
It would be possible have some sources which are GPLv2+ only, but I
think we want to avoid such complications.
It's not about sources. Assuming the font-exception doesn't apply,
this only means all binaries linking to libsystemd-gfx are GPLv2. The
sources stay LGPL as usual.
It is also about sources. If you include unifont.hex in the systemd tarball,
the distribution of the tarball also has to satisfy the license of unifont.hex.
Post by David Herrmann
Post by Zbigniew Jędrzejewski-Szmek
Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
There's no reason beside license issues.
Post by David Herrmann
Post by Zbigniew Jędrzejewski-Szmek
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
That's what I currently do.
Post by Zbigniew Jędrzejewski-Szmek
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
Where is the difference between build-time linking and mmap()?
(regarding licensing)
With build-time linking the resulting binary is a derivative work of
all sources. With mmap you can replace the font file by something
different without any trouble, at least theoretically, so there's no
derivative work and no license issue.
Post by David Herrmann
Also, where's the point of keeping libsystemd-gfx.so LGPL just to have
a *mandatory* dependency which is GPL?
We can have dependencies which are GPL only, e.g. dbus. But we don't really
care if you can use systemd without GPL-only stuff. The point is to maintain
consistency between individual components and the declared LGPLv2+ license
of the systemd tarball.

Zbyszek
Zbigniew Jędrzejewski-Szmek
2013-12-01 14:48:39 UTC
Permalink
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
On Sun, Dec 1, 2013 at 6:28 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
There's no reason beside license issues.
Ooops, I was too fast here - there's also the issue of distributions
wanting to avoid duplicated sources, whether it be source code or
fonts or anything else.

I think that there's little point to explore alternative solutions and
wasting more time on this. I'm now pretty sure that assuming that
unifont.hex is available as a compilation time dependency and
transforming it and including in the systemd-gfx binary is the proper
thing to do.

Zbyszek
David Herrmann
2013-12-01 14:58:16 UTC
Permalink
Hi

On Sun, Dec 1, 2013 at 3:48 PM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
On Sun, Dec 1, 2013 at 6:28 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Also, if the font was embedded in systemd, distributions would then
remove it in order to replace is with the system version. So I think
that including the font sources is pointless... Debian has it packaged [1],
but an old version, I'm not sure if there have been recent updates, and
possibly in the wrong format. Fedora doesn't seem to have it yet.
But adding fonts is easy, I'd do the Fedora package myself, and other
distributions could surely add/update it.
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
There's no reason beside license issues.
Ooops, I was too fast here - there's also the issue of distributions
wanting to avoid duplicated sources, whether it be source code or
fonts or anything else.
I think that there's little point to explore alternative solutions and
wasting more time on this. I'm now pretty sure that assuming that
unifont.hex is available as a compilation time dependency and
transforming it and including in the systemd-gfx binary is the proper
thing to do.
Thanks for the comments. I agree that link-time inclusion works best.
I also sent an email to the unifoundry-developers. I hope they can
give a clear statement. But the least we can do is let them know that
we're unsure.

Thanks
David
Zbigniew Jędrzejewski-Szmek
2013-12-02 04:26:28 UTC
Permalink
https://bugzilla.redhat.com/show_bug.cgi?id=1036462
Lennart Poettering
2013-12-11 00:56:18 UTC
Permalink
Post by David Herrmann
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
Post by Zbigniew Jędrzejewski-Szmek
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
That's what I currently do.
Post by Zbigniew Jędrzejewski-Szmek
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
Where is the difference between build-time linking and mmap()?
(regarding licensing)
Also, where's the point of keeping libsystemd-gfx.so LGPL just to have
a *mandatory* dependency which is GPL?
I think embedding the thing into our binary in question is the best
choice here. I don't see any license problems, and it's certainly the
fastest and most robust thing to do...

Lennart
--
Lennart Poettering, Red Hat
Kay Sievers
2013-12-11 01:14:26 UTC
Permalink
On Wed, Dec 11, 2013 at 1:56 AM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
Post by Zbigniew Jędrzejewski-Szmek
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
That's what I currently do.
Post by Zbigniew Jędrzejewski-Szmek
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
Where is the difference between build-time linking and mmap()?
(regarding licensing)
Also, where's the point of keeping libsystemd-gfx.so LGPL just to have
a *mandatory* dependency which is GPL?
I think embedding the thing into our binary in question is the best
choice here. I don't see any license problems, and it's certainly the
fastest and most robust thing to do...
It is huge, ~2MB. We need to be sure that we will never have 2
different processes carrying it, it would be a waste of memory.

Storing it on disk and mmap()ing it from multiple different binaries
would be a fine alternative then, I think.

Kay
Lennart Poettering
2013-12-11 01:20:04 UTC
Permalink
Post by Kay Sievers
On Wed, Dec 11, 2013 at 1:56 AM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
I'm fine with installing the file into the system, but I doubt we win
much. It's meant as fallback for early-boot, initrd and so on. If we
keep it separate, we must make sure to include it in any systems we
build (initrd, containers, vms, ..). So if there's no reason beside
license issues, I'd like to keep it built-in.
Post by Zbigniew Jędrzejewski-Szmek
So if it is acceptable for systemd-gfx *binary* to be GPLv2+ licensed,
we could use the system unifont.hex file at build time, and actually
link it into the binary. I propose that we try to go this way.
That's what I currently do.
Post by Zbigniew Jędrzejewski-Szmek
Or we could have the package also contain the converted font in appropriate
format, and mmap it at runtime. But this is more complex, and doesn't actually
avoid the licensing issue, since the font would still be GPLv2+.
Where is the difference between build-time linking and mmap()?
(regarding licensing)
Also, where's the point of keeping libsystemd-gfx.so LGPL just to have
a *mandatory* dependency which is GPL?
I think embedding the thing into our binary in question is the best
choice here. I don't see any license problems, and it's certainly the
fastest and most robust thing to do...
It is huge, ~2MB. We need to be sure that we will never have 2
different processes carrying it, it would be a waste of memory.
That's a very good point. Given that we link everything in systemd
statically so far, then this is a strong point to store in an external
mmapable binary file if this is used by more than one process...

Lennart
--
Lennart Poettering, Red Hat
Lennart Poettering
2013-12-11 00:55:00 UTC
Permalink
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
I don't think this is really a problem, is it? LGPL2+ is after all
compatible wit GPL2+. When you link stuff that is LGPL2+ with stuff that
is GPL2+ you effectively just "downgrade" LGPL2+ to GPL2+, and get a
result that effectively should be treated as GPL2+ altogether.

Now, what matters for us is that all our APIs and our sources stay
LGPL2+, so that we can freely copy/paste code around and library users
can use the stuff with little restrictions. However, if systemd-consoled
at runtime is downgraded to GPL2+, then that should not really affect
anybody... I mean, if somebody wanted to link non-free code into
consoled, then we'd have a problem, but nobody does that and consoled is
not a program where that's even something to think off.

I'd be fine to do with unifont what we do with the pci/usb-db, and ship
it in the sources, and provide an easy way to update it from upstream
(via a make target).

Lennart
--
Lennart Poettering, Red Hat
Zbigniew Jędrzejewski-Szmek
2013-12-11 01:10:27 UTC
Permalink
Post by Lennart Poettering
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont.
Hi David,
I don't think that GNU-Unifont is licensed in a way that allows it to
be embedded in systemd. Systemd is LGPLv2+, while Unifont is GPLv2+ + FontException.
I don't think this is really a problem, is it? LGPL2+ is after all
compatible wit GPL2+. When you link stuff that is LGPL2+ with stuff that
is GPL2+ you effectively just "downgrade" LGPL2+ to GPL2+, and get a
result that effectively should be treated as GPL2+ altogether.
Now, what matters for us is that all our APIs and our sources stay
LGPL2+, so that we can freely copy/paste code around and library users
can use the stuff with little restrictions. However, if systemd-consoled
at runtime is downgraded to GPL2+, then that should not really affect
anybody... I mean, if somebody wanted to link non-free code into
consoled, then we'd have a problem, but nobody does that and consoled is
not a program where that's even something to think off.
I'd be fine to do with unifont what we do with the pci/usb-db, and ship
it in the sources, and provide an easy way to update it from upstream
(via a make target).
All this is to some extent true, even if mixing LGPL and GPL code makes
things more complicated for downstreams.

Above notwithstanding, the first thing that Fedora and Debian are going
to do is to rip out our version and replace it with a distribution wide
copy. I'd prefer to make things easier and keep unifont fully external.

Zbyszek
Shawn Landden
2013-12-02 15:26:20 UTC
Permalink
Post by David Herrmann
If we want to interact with a user in the initrd, during
emergency-situations, in single-user mode, or in any other rather limited
situation, we currently rely on the kernel to do input and graphics access
for us. More precisely, we rely on the VT layer to do keyboard parsing and
font rendering. Or in other words: The *kernel* provides our UI!
This is bad for the same reasons we don't put any other UI into the
kernel. However, there are a few reasons to keep a minimal UI in the
1) show panic-screen with kernel oops message
2) show log-screen during early boot
3) allow kernel-debugging via kdb
While people using kdb are encouraged to keep VTs, for 1) and 2) there is
a replacement via fblog/drmlog.
So we run out of reasons to keep a UI in the kernel. However, to allow
- keyboard handling: convert keycodes into keysyms and modifiers/..
- modesetting: program the gfx pipeline and provide a rendering
infrastructure
- fonts: to render text, we need some basic fonts
- hotplugging: device detection and assignment during runtime
(Note that all these are implemented (often quite rudimentary) in the
kernel to allow a basic UI.)
This patch introduces sd-gfx, a systemd-internal library dealing with all
these things. Note that it is designed to be exported some day, but for
now we keep it internal.
While the library itself will be kept small and almost self-contained, it
is designed to be extensible and can be used in rather complex graphics
applications. For systemd, we plan to add a basic emergency-console, an
initrd password-query, kernel-log-screen and a fallback login-screen.
These allow booting without CONFIG_VT and provide a basic system for
seats without VTs (either CONFIT_VT=n or seats != seat0).
As a first step, we add the required header+build-chain and add the
font-handling. To avoid heavy font-pipelines in systemd, we only provide
a statically-sized fallback-font based on GNU-Unifont. It contains glyphs
for the *whole* Base-Multilingual-Plane of Unicode and thus allows
internationalized text.
The "make-unifont.py" script is used by the "make update-unifont" custom
target to regenerate the unifont files. As this can take quite some time,
we check the result into git so only maintainers need to run this.
The binary file contains all glyphs in a compressed 1-bit-per-pixel format
for all 8x16 and 16x16 glyphs. It is linked directly into libsystemd-gfx
via the *.bin makefile target. Binary size is 2.1MB, but thanks to paging,
the kernel only loads required pages into memory. A ASCII-only screen thus
only needs 40k VIRT mem.
Do we have quad-glyphs (16x32) for the ASCII part of this for hi-res screens?

(also, if we use NFD we could reuse these for some of unicode[1], but
that is less important)

[1]http://blog.golang.org/normalization
Post by David Herrmann
---
Hi
I removed the unifont-data from this patch so this will not apply cleanly.
However, the ML would reject the huge patch otherwise. If anyone is interested
http://cgit.freedesktop.org/~dvdhrm/systemd/log/?h=console
Thanks
David
Makefile.am | 31 +
configure.ac | 10 +
make-unifont.py | 138 +
src/libsystemd-gfx/.gitignore | 1 +
src/libsystemd-gfx/Makefile | 1 +
src/libsystemd-gfx/gfx-unifont.c | 273 +
src/libsystemd-gfx/unifont.bin | Bin 0 -> 2162688 bytes
src/libsystemd-gfx/unifont.hex | 63488 +++++++++++++++++++++++++++++++++++++
src/systemd/sd-gfx.h | 65 +
9 files changed, 64007 insertions(+)
create mode 100755 make-unifont.py
create mode 100644 src/libsystemd-gfx/.gitignore
create mode 120000 src/libsystemd-gfx/Makefile
create mode 100644 src/libsystemd-gfx/gfx-unifont.c
create mode 100644 src/libsystemd-gfx/unifont.bin
create mode 100644 src/libsystemd-gfx/unifont.hex
create mode 100644 src/systemd/sd-gfx.h
diff --git a/Makefile.am b/Makefile.am
index ce27e82..2edb091 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3853,6 +3853,37 @@ EXTRA_DIST += \
endif
# ------------------------------------------------------------------------------
+if HAVE_GFX
+noinst_LTLIBRARIES += \
+ libsystemd-gfx.la
+
+libsystemd_gfx_la_SOURCES = \
+ src/libsystemd-gfx/sd-gfx.h \
+ src/libsystemd-gfx/gfx-unifont.c
+
+libsystemd_gfx_la_CFLAGS = \
+ $(AM_CFLAGS)
+
+libsystemd_gfx_la_LIBADD = \
+ libsystemd-shared.la \
+ src/libsystemd-gfx/unifont.bin.lo
+
+src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
+
+src/libsystemd-gfx/unifont.cmp: make-unifont.py src/libsystemd-gfx/unifont.bin
+
+update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp
+ if test "x$$?" != "x0" -o "x$$RET" != "x0" ; then \
+ echo "Generated Unifont-file differs from original; generator probably broken" ; \
+ exit 1 ; \
+ fi
+endif
+
+# ------------------------------------------------------------------------------
if ENABLE_NETWORKD
rootlibexec_PROGRAMS += \
systemd-networkd
diff --git a/configure.ac b/configure.ac
index 3fd05da..354673a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -291,6 +291,15 @@ fi
AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
# ------------------------------------------------------------------------------
+have_gfx=no
+AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
+if test "x$enable_gfx" != "xno"; then
+ AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built])
+ have_gfx=yes
+fi
+AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
+
+# ------------------------------------------------------------------------------
have_blkid=no
AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support]))
if test "x$enable_blkid" != "xno"; then
@@ -1079,6 +1088,7 @@ AC_MSG_RESULT([
polkit: ${have_polkit}
efi: ${have_efi}
kmod: ${have_kmod}
+ sd-gfx: ${have_gfx}
blkid: ${have_blkid}
nss-myhostname: ${have_myhostname}
gudev: ${enable_gudev}
diff --git a/make-unifont.py b/make-unifont.py
new file mode 100755
index 0000000..375c8a4
--- /dev/null
+++ b/make-unifont.py
@@ -0,0 +1,138 @@
+# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
+#
+# This file is part of systemd.
+#
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Parse a unifont.hex file and produce a compressed binary-format we use in
+# our gfx applications. Can also do the reverse to verify correctness.
+#
+
+from __future__ import print_function
+import re
+import sys
+import fileinput
+import struct
+
+#
+# Write "bits" array as binary output.
+#
+
+ l = len(entry)
+ entry = "0" * 64
+ l = 0
+ entry += "0" * (64 - l)
+
+ sys.stdout.buffer.write(struct.pack('B', int(l / 32)))
+
+ c = int(entry[i:i+2], 16)
+ sys.stdout.buffer.write(struct.pack('B', c))
+
+ write_bin_entry(bits[idx])
+
+#
+# Parse hex file into "bits" array
+#
+
+ m = re.match(r"^([0-9A-Fa-f]+):([0-9A-Fa-f]+)$", line)
+ return
+
+ idx = int(m.group(1), 16)
+ val = m.group(2)
+
+ # insert skipped lines
+ bits.append("")
+
+ bits.insert(idx, val)
+
+ bits = []
+
+ continue
+ continue
+
+ parse_hex_line(bits, line)
+
+ return bits
+
+#
+# Write "bits" array as text-file line-by-line to stdout.
+#
+
+ print("%04X:%s" % (idx, entry))
+
+ continue
+
+ write_hex_line(idx, entry)
+
+#
+# Parse a binary file into "bits" so we can verify the correctness of a given
+# binary file.
+#
+
+ entry = ""
+ l = struct.unpack('B', chunk[0:1])[0]
+ entry += format(c, "02X")
+
+ bits.append(entry)
+
+ bits = []
+
+ chunk = sys.stdin.buffer.read(33)
+ parse_bin_entry(bits, chunk)
+ break
+
+ return bits
+
+#
+# In normal mode we simply read line by line from standard-input (or first
+# argument) and write the binary-file to standard-output.
+#
+# In verify-mode, we read the binary-file from standard-input and write the
+# text-file line-by-line to standard-output.
+#
+
+ bits = parse_bin()
+ write_hex(bits)
+ bits = parse_hex()
+ write_bin(bits)
diff --git a/src/libsystemd-gfx/.gitignore b/src/libsystemd-gfx/.gitignore
new file mode 100644
index 0000000..a792d8f
--- /dev/null
+++ b/src/libsystemd-gfx/.gitignore
@@ -0,0 +1 @@
+/unifont.cmp
diff --git a/src/libsystemd-gfx/Makefile b/src/libsystemd-gfx/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/libsystemd-gfx/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/libsystemd-gfx/gfx-unifont.c b/src/libsystemd-gfx/gfx-unifont.c
new file mode 100644
index 0000000..c2fc879
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-unifont.c
@@ -0,0 +1,273 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "def.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+/* Glyphs are linked as binary data. The data layout is a size-byte followed by
+ * 32 data bytes. The data bytes are padded with 0 if the size is smaller than
+ * 32. The size-byte specifies the size of a single line. Each glyph always
+ * consists of 16 lines.
+ * Currently, size==1 is used for single-width glyphs and size==2 for
+ * double-width glyphs. */
+
+struct unifont_data {
+ uint8_t cells;
+ uint8_t data[32];
+} _packed_;
+
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_start[];
+extern const struct unifont_data _binary_src_libsystemd_gfx_unifont_bin_end[];
+static const unsigned int unifont_width = 8;
+static const unsigned int unifont_stride = 1;
+static const unsigned int unifont_max_cells = 2;
+static const unsigned int unifont_height = 16;
+
+static const struct unifont_data unifont_fallback = {
+ .cells = 1,
+ .data = "\x00\x00\x00\x7E\x66\x5A\x5A\x7A\x76\x76\x7E\x76\x76\x7E\x00\x00",
+};
+
+static const struct unifont_data *unifont_get(uint32_t ucs4) {
+ const struct unifont_data *begin, *end, *g;
+
+ begin = _binary_src_libsystemd_gfx_unifont_bin_start;
+ end = _binary_src_libsystemd_gfx_unifont_bin_end;
+
+ g = &begin[ucs4];
+ if (g >= end)
+ return &unifont_fallback;
+ if (g->cells == 0 || g->cells > unifont_max_cells)
+ return &unifont_fallback;
+
+ return g;
+}
+
+/*
+ * Fonts
+ * We provide a built-in static font. This can be used to render text in any
+ * situation without depending on huge text-pipelines like pango. The font is
+ * based on "GNU Unifont" which provides fixed-size glyphs for the whole
+ * Unicode Basic Multilingual Plane.
+ * This can be used in initrds, emergency-consoles and other situations where a
+ * full text-pipeline would be overkill.
+ *
+ * Note that we provide some enhanced features to properly support all systems.
+ * - scaling: You can specify a "ppi" value to scale the static fonts. This
+ * can only be done with integer-scaling and should only be used
+ * as reading-aid or for high-DPI screens.
+ * - combining: Glyph-combining (eg., for unicode combining characters) is
+ * implemented in the renderer to correctly draw combined
+ * characters for non-latin scripts. Character-combining is left
+ * to the caller, we only allow drawing a set of characters as a
+ * single glyph.
+ * - caching: To support fast lookups we cache modified glyphs.
+ */
+
+typedef struct gfx_glyph gfx_glyph;
+
+struct gfx_glyph {
+ sd_gfx_buffer buf;
+};
+
+struct sd_gfx_font {
+ unsigned int ppi;
+ unsigned int width;
+ unsigned int height;
+ Hashmap *glyphs;
+
+ gfx_glyph fallback;
+};
+
+static void gfx_glyph_blend(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ unsigned int i, j;
+ const uint8_t *src;
+ uint8_t *dst;
+
+ /*
+ */
+
+ src = u->data;
+ dst = g->buf.data;
+
+ for (i = 0; i < unifont_height; ++i) {
+ for (j = 0; j < u->cells; ++j)
+ dst[j] |= src[j];
+
+ src += u->cells * unifont_stride;
+ dst += g->buf.stride;
+ }
+}
+
+static int gfx_glyph_render(gfx_glyph *g, unsigned int ppi, const struct unifont_data *u) {
+ g->buf.width = u->cells * unifont_width;
+ g->buf.height = unifont_height;
+ g->buf.stride = u->cells * unifont_stride;
+ g->buf.format = SD_GFX_BUFFER_FORMAT_A1;
+
+ g->buf.data = calloc(unifont_height, unifont_max_cells * unifont_stride);
+ if (!g->buf.data)
+ return -ENOMEM;
+
+ gfx_glyph_blend(g, ppi, u);
+ return 0;
+}
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi) {
+ sd_gfx_font *font;
+ int r;
+
+ ppi = CLAMP(ppi, 10U, 1000U);
+
+ font = calloc(1, sizeof(*font));
+ if (!font)
+ return log_oom();
+
+ font->ppi = ppi;
+ font->width = unifont_width;
+ font->height = unifont_height;
+
+ font->glyphs = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!font->glyphs) {
+ free(font);
+ return log_oom();
+ }
+
+ r = gfx_glyph_render(&font->fallback, font->ppi, &unifont_fallback);
+ if (r < 0) {
+ hashmap_free(font->glyphs);
+ free(font);
+ return log_oom();
+ }
+
+ *out = font;
+ return 0;
+}
+
+static void gfx_glyph_free(gfx_glyph *g) {
+ free(g->buf.data);
+ free(g);
+}
+
+void sd_gfx_font_free(sd_gfx_font *font) {
+ gfx_glyph *g;
+
+ if (!font)
+ return;
+
+ while ((g = hashmap_steal_first(font->glyphs)))
+ gfx_glyph_free(g);
+
+ hashmap_free(font->glyphs);
+ free(font);
+}
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font) {
+ return font->ppi;
+}
+
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font) {
+ return font->width;
+}
+
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font) {
+ return font->height;
+}
+
+/*
+ * Render a glyph
+ * This first tries to look up the glyph in the cache. If found, it is returned,
+ * otherwise the glyph is rendered and put into the cache.
+ *
+ * This function never fails. If the glyph cannot be rendered, a fallback glyph
+ * is returned. Note that this function can render multiple glyphs into a single
+ * However, this should only be used for combining-characters.
+ *
+ * Whenever you render a glyph, this functions needs a unique ID to identify it.
+ * automatically taken as id). But if you render combined codepoints, you need
+ * to allocate a unique ID for them. See libtsm for an example how to do that.
+ *
+ * it yourself.
+ */
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out) {
+ const struct unifont_data *u;
+ gfx_glyph *g;
+
+ if (!len)
+ goto error;
+
+ if (!id)
+ id = *ucs4;
+
+ g = hashmap_get(font->glyphs, INT_TO_PTR(id));
+ if (g)
+ goto out;
+
+ g = calloc(1, sizeof(*g));
+ if (!g) {
+ log_oom();
+ goto error;
+ }
+
+ u = unifont_get(*ucs4);
+ if (gfx_glyph_render(g, font->ppi, u) < 0) {
+ log_oom();
+ free(g);
+ goto error;
+ }
+
+ while (--len) {
+ u = unifont_get(*++ucs4);
+ gfx_glyph_blend(g, font->ppi, u);
+ }
+
+ if (hashmap_put(font->glyphs, INT_TO_PTR(id), g) < 0) {
+ log_oom();
+ free(g->buf.data);
+ free(g);
+ goto error;
+ }
+
+ goto out;
+
+ g = &font->fallback;
+ *out = &g->buf;
+}
diff --git a/src/libsystemd-gfx/unifont.bin b/src/libsystemd-gfx/unifont.bin
new file mode 100644
index 0000000000000000000000000000000000000000..0b32d80222dd465d0612188915ef67355dc73e4d
GIT binary patch
literal 2162688
z9F*YJIJ$6q>;c^VB)T=i&<gm^zxN+SppMS|-OpAY!~fZzt8D^U-P?cj(n~*i<Be_n
[... skipped for ML ...]
h<$hpdYOEeTz{h7Yzz4==k?}x2v&LpIyNUqW`UBL12zCGf
literal 0
HcmV?d00001
diff --git a/src/libsystemd-gfx/unifont.hex b/src/libsystemd-gfx/unifont.hex
new file mode 100644
index 0000000..90672a8
--- /dev/null
+++ b/src/libsystemd-gfx/unifont.hex
@@ -0,0 +1,63488 @@
+0000:AAAA00018000000180004A51EA505A51C99E0001800000018000000180005555
+0001:AAAA00018000000180003993C252325F8A527193800000018000000180005555
[... skipped for ML ...]
+FFFE:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEF87FFFFFFFF
+FFFF:FFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFFE187EFBFE38FEFBFEFBFFFFFFFFF
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
new file mode 100644
index 0000000..c46fbd4
--- /dev/null
+++ b/src/systemd/sd-gfx.h
@@ -0,0 +1,65 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosdgfxhfoo
+#define foosdgfxhfoo
+
+/***
+ This file is part of systemd.
+
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <systemd/_sd-common.h>
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_gfx_buffer sd_gfx_buffer;
+typedef struct sd_gfx_font sd_gfx_font;
+
+/* memory buffer */
+
+enum {
+ SD_GFX_BUFFER_FORMAT_A1,
+ SD_GFX_BUFFER_FORMAT_A8,
+ SD_GFX_BUFFER_FORMAT_XRGB8888,
+};
+
+struct sd_gfx_buffer {
+ unsigned int width;
+ unsigned int height;
+ int stride;
+ unsigned int format;
+ void *data;
+};
+
+/* unifont */
+
+int sd_gfx_font_new(sd_gfx_font **out, unsigned int ppi);
+void sd_gfx_font_free(sd_gfx_font *font);
+
+unsigned int sd_gfx_font_get_ppi(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_width(sd_gfx_font *font);
+unsigned int sd_gfx_font_get_height(sd_gfx_font *font);
+
+void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);
+
+_SD_END_DECLARATIONS;
+
+#endif
--
1.8.4.2
_______________________________________________
systemd-devel mailing list
http://lists.freedesktop.org/mailman/listinfo/systemd-devel
David Herrmann
2013-12-02 15:38:00 UTC
Permalink
Hi
Post by Shawn Landden
Post by David Herrmann
The binary file contains all glyphs in a compressed 1-bit-per-pixel format
for all 8x16 and 16x16 glyphs. It is linked directly into libsystemd-gfx
via the *.bin makefile target. Binary size is 2.1MB, but thanks to paging,
the kernel only loads required pages into memory. A ASCII-only screen thus
only needs 40k VIRT mem.
Do we have quad-glyphs (16x32) for the ASCII part of this for hi-res screens?
(also, if we use NFD we could reuse these for some of unicode[1], but
that is less important)
[1]http://blog.golang.org/normalization
See here:

+static void gfx_glyph_blend(gfx_glyph *g, unsigned int ppi, const
struct unifont_data *u) {
+ unsigned int i, j;
+ const uint8_t *src;
+ uint8_t *dst;
+
+ /*
+ * TODO: scale glyph according to @ppi
+ */

Idea is to simply scale the glyph by an integer (sth like max(1, ppi /
72)). This will result in a readable font-size on all screens. Note
that we don't care for hi-res fonts, we're not EFI-firmware..

Regarding NFD: I don't care for normalization. I don't do any
string-comparisons. We support combining characters in the
font-renderer just fine, so I don't think we ever need unicode
normalization.

Thanks
David
David Herrmann
2013-11-27 18:48:46 UTC
Permalink
The test-gl helper shows how sd_gfx_card can be used to get a full OpenGL
context on the device. It is not added to the build-tools as it requires
mesa and might break on Khronos header-updates (yes, they break API *and*
ABI compatibility often!).
---
.gitignore | 1 +
Makefile.am | 18 +++
configure.ac | 3 +
src/libsystemd-gfx/test-gl.c | 342 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 364 insertions(+)
create mode 100644 src/libsystemd-gfx/test-gl.c

diff --git a/.gitignore b/.gitignore
index a61f68d..c856412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,6 +116,7 @@
/test-event
/test-fileio
/test-gfx
+/test-gl
/test-hashmap
/test-hostname
/test-id128
diff --git a/Makefile.am b/Makefile.am
index aa17876..1e8aeed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3886,6 +3886,19 @@ test_gfx_LDADD = \
libsystemd-shared.la \
libsystemd-gfx.la

+test_gl_SOURCES = \
+ src/libsystemd-gfx/test-gl.c
+
+test_gl_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GFX_GL_CFLAGS)
+
+test_gl_LDADD = \
+ $(GFX_GL_LIBS) \
+ libsystemd-bus-internal.la \
+ libsystemd-shared.la \
+ libsystemd-gfx.la
+
test_kbd_SOURCES = \
src/libsystemd-gfx/test-kbd.c

@@ -3903,6 +3916,11 @@ tests += \
test-gfx \
test-kbd

+if HAVE_GFX_GL
+# Uncomment this to enable test-gl builds
+#tests += test-gl
+endif
+
src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
$(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.hex | $(PYTHON) $< >$@

diff --git a/configure.ac b/configure.ac
index b76a86d..bf3fce3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -299,8 +299,11 @@ if test "x$enable_gfx" != "xno"; then
if test "x$have_gfx" = xno -a "x$enable_gfx" = xyes; then
AC_MSG_ERROR([*** sd-gfx support requested, but libraries not found])
fi
+ PKG_CHECK_MODULES(GFX_GL, [ libdrm >= 2.4.47 gbm >= 9.2.3 egl glesv2 ],
+ [AC_DEFINE(HAVE_GFX_GL, 1, [Define if sd-gfx GL examples are built]) have_gfx_gl=yes], have_gfx_gl=no)
fi
AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
+AM_CONDITIONAL(HAVE_GFX_GL, [test "$have_gfx_gl" = "yes"])

# ------------------------------------------------------------------------------
have_blkid=no
diff --git a/src/libsystemd-gfx/test-gl.c b/src/libsystemd-gfx/test-gl.c
new file mode 100644
index 0000000..733b451
--- /dev/null
+++ b/src/libsystemd-gfx/test-gl.c
@@ -0,0 +1,342 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <drm.h>
+#include <drm_fourcc.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <gbm.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+struct plane {
+ struct gbm_surface *gbm;
+ EGLSurface egl;
+
+ struct gbm_surface *t_gbm;
+ EGLSurface t_egl;
+};
+
+static int gl_fd;
+static struct gbm_device *gl_device;
+static EGLDisplay gl_display;
+static EGLConfig gl_config;
+static EGLContext gl_context;
+
+static void err(const char *format, ...) {
+ va_list args;
+
+ fprintf(stderr, "ERROR: ");
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+
+ _exit(1);
+}
+
+static void fb_unlink(sd_gfx_fb *fb, void *fn_data) {
+ struct gbm_bo *bo = fn_data;
+
+ gbm_bo_set_user_data(bo, NULL, NULL);
+}
+
+static void fb_unpin(sd_gfx_fb *fb, void *fn_data) {
+ struct gbm_bo *bo = fn_data;
+ sd_gfx_plane *plane = sd_gfx_fb_get_plane(fb);
+ struct plane *p = sd_gfx_plane_get_fn_data(plane);
+
+ gbm_surface_release_buffer(p->gbm, bo);
+}
+
+static void fb_destroy(struct gbm_bo *bo, void *data) {
+ sd_gfx_fb *fb = data;
+
+ if (!fb)
+ return;
+
+ err("fb_destroy()");
+}
+
+static sd_gfx_fb *bo_to_fb(sd_gfx_plane *plane, struct gbm_bo *bo) {
+ sd_gfx_fb *fb;
+ int r;
+
+ fb = gbm_bo_get_user_data(bo);
+ if (fb)
+ return fb;
+
+ r = sd_gfx_fb_new_rgb(&fb,
+ plane,
+ DRM_FORMAT_XRGB8888,
+ gbm_bo_get_handle(bo).u32,
+ gbm_bo_get_width(bo),
+ gbm_bo_get_height(bo),
+ gbm_bo_get_stride(bo),
+ 0, 0, 0);
+ if (r < 0)
+ return NULL;
+
+ sd_gfx_fb_set_fn_data(fb, bo);
+ sd_gfx_fb_set_fns(fb, fb_unlink, fb_unpin);
+ gbm_bo_set_user_data(bo, fb, fb_destroy);
+
+ return fb;
+}
+
+static int plane_prepare(sd_gfx_plane *plane, void *fn_data, unsigned int width, unsigned int height, sd_gfx_fb **fb) {
+ struct plane *p = fn_data;
+ struct gbm_bo *bo;
+
+ p->t_gbm = gbm_surface_create(gl_device, width, height, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (!p->t_gbm)
+ return -ENOMEM;
+
+ p->t_egl = eglCreateWindowSurface(gl_display, gl_config,
+ (EGLNativeWindowType)p->t_gbm, NULL);
+ if (p->t_egl == EGL_NO_SURFACE)
+ goto err_gbm;
+
+ if (!eglMakeCurrent(gl_display, p->t_egl, p->t_egl, gl_context))
+ goto err_egl;
+
+ glClearColor(0.5, 0.4, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ if (!eglSwapBuffers(gl_display, p->t_egl))
+ goto err_ctx;
+
+ bo = gbm_surface_lock_front_buffer(p->t_gbm);
+ if (!bo)
+ goto err_ctx;
+
+ *fb = bo_to_fb(plane, bo);
+ if (!*fb)
+ goto err_unlock;
+
+ return 0;
+
+err_unlock:
+ gbm_surface_release_buffer(p->t_gbm, bo);
+err_ctx:
+ eglMakeCurrent(gl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+err_egl:
+ eglDestroySurface(gl_display, p->t_egl);
+err_gbm:
+ gbm_surface_destroy(p->t_gbm);
+ return -EINVAL;
+}
+
+static void plane_cancel(sd_gfx_plane *plane, void *fn_data, sd_gfx_fb *fb) {
+ struct plane *p = fn_data;
+ struct gbm_bo *bo = sd_gfx_fb_get_fn_data(fb);
+
+ gbm_surface_release_buffer(p->t_gbm, bo);
+
+ gbm_bo_set_user_data(bo, NULL, NULL);
+ sd_gfx_fb_set_fn_data(fb, NULL);
+ sd_gfx_fb_set_fns(fb, NULL, NULL);
+
+ eglMakeCurrent(gl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(gl_display, p->t_egl);
+ gbm_surface_destroy(p->t_gbm);
+
+ p->t_egl = NULL;
+ p->t_gbm = NULL;
+}
+
+static void plane_finish(sd_gfx_plane *plane, void *fn_data, sd_gfx_fb *fb) {
+ struct plane *p = fn_data;
+
+ if (p->gbm) {
+ eglDestroySurface(gl_display, p->egl);
+ gbm_surface_destroy(p->gbm);
+ }
+
+ p->egl = p->t_egl;
+ p->gbm = p->t_gbm;
+ p->t_egl = NULL;
+ p->t_gbm = NULL;
+}
+
+static int plane_create(sd_gfx_plane *plane) {
+ struct plane *p;
+
+ p = calloc(1, sizeof(*p));
+ if (!p)
+ return -ENOMEM;
+
+ sd_gfx_plane_set_fn_data(plane, p);
+ sd_gfx_plane_set_fns(plane, plane_prepare, plane_cancel, plane_finish);
+
+ return 0;
+}
+
+static void plane_destroy(sd_gfx_plane *plane) {
+ struct plane *p = sd_gfx_plane_get_fn_data(plane);
+
+ if (!p)
+ return;
+
+ sd_gfx_plane_set_fn_data(plane, NULL);
+ sd_gfx_plane_set_fns(plane, NULL, NULL, NULL);
+ free(p);
+}
+
+static void pipe_create(sd_gfx_pipe *pipe) {
+ sd_gfx_plane *plane = sd_gfx_pipe_get_primary_plane(pipe);
+ int r;
+
+ r = plane_create(plane);
+ if (r < 0)
+ err("plane_create(): %d", r);
+}
+
+static void pipe_destroy(sd_gfx_pipe *pipe) {
+ sd_gfx_plane *plane = sd_gfx_pipe_get_primary_plane(pipe);
+
+ plane_destroy(plane);
+}
+
+static void card_event_fn(sd_gfx_card *card, void *data, sd_gfx_card_event *ev) {
+ switch (ev->type) {
+ case SD_GFX_CARD_PIPE_CREATE:
+ log_debug("pipe %u create", sd_gfx_pipe_get_id(ev->pipe));
+ pipe_create(ev->pipe);
+ break;
+ case SD_GFX_CARD_PIPE_DESTROY:
+ log_debug("pipe %u destroy", sd_gfx_pipe_get_id(ev->pipe));
+ pipe_destroy(ev->pipe);
+ break;
+ }
+}
+
+static void init_egl(void) {
+ static const EGLint conf_att[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 0,
+ EGL_NONE,
+ };
+ static const EGLint ctx_att[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ EGLint major, minor, n;
+ EGLenum api;
+
+ gl_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (gl_fd < 0)
+ err("open(): %m");
+
+ gl_device = gbm_create_device(gl_fd);
+ if (!gl_device)
+ err("gbm_create_device(): %m");
+
+ gl_display = eglGetDisplay((EGLNativeDisplayType)gl_device);
+ if (gl_display == EGL_NO_DISPLAY)
+ err("eglGetDisplay(): %m");
+
+ if (!eglInitialize(gl_display, &major, &minor))
+ err("eglInitialize(): %m");
+
+ log_debug("EGL Init %d.%d", major, minor);
+ log_debug("EGL Version %s", eglQueryString(gl_display, EGL_VERSION));
+ log_debug("EGL Vendor %s", eglQueryString(gl_display, EGL_VENDOR));
+ log_debug("EGL Extensions %s", eglQueryString(gl_display, EGL_EXTENSIONS));
+
+ api = EGL_OPENGL_ES_API;
+ if (!eglBindAPI(api))
+ err("eglBindAPI(): %m");
+
+ if (!eglChooseConfig(gl_display, conf_att, &gl_config, 1, &n) || n != 1)
+ err("eglChooseConfig(%d): %m", (int)n);
+
+ gl_context = eglCreateContext(gl_display, gl_config, EGL_NO_CONTEXT, ctx_att);
+ if (gl_context == EGL_NO_CONTEXT)
+ err("eglCreateContext(): %m");
+}
+
+static void destroy_egl(void) {
+ eglMakeCurrent(gl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(gl_display, gl_context);
+ eglTerminate(gl_display);
+ gbm_device_destroy(gl_device);
+ close(gl_fd);
+}
+
+int main(int argc, char **argv) {
+ sd_event *event;
+ sd_gfx_card *card;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ r = sd_event_new(&event);
+ if (r < 0)
+ err("event_new(): %d", r);
+
+ init_egl();
+
+ r = sd_gfx_card_new(&card, "/dev/dri/card0", gl_fd, event);
+ if (r < 0)
+ err("card_new(): %d", r);
+ sd_gfx_card_set_event_fn(card, card_event_fn);
+
+ r = sd_gfx_card_wake_up(card);
+ if (r < 0)
+ err("card_wake_up(): %d", r);
+
+ log_info("wait 2s..");
+ sleep(2);
+
+ sd_gfx_card_restore(card);
+ sd_gfx_card_free(card);
+
+ destroy_egl();
+ sd_event_unref(event);
+
+ return 0;
+}
--
1.8.4.2
Zbigniew Jędrzejewski-Szmek
2013-12-01 05:21:36 UTC
Permalink
Post by David Herrmann
The test-gl helper shows how sd_gfx_card can be used to get a full OpenGL
context on the device. It is not added to the build-tools as it requires
mesa and might break on Khronos header-updates (yes, they break API *and*
ABI compatibility often!).
---
.gitignore | 1 +
Makefile.am | 18 +++
configure.ac | 3 +
src/libsystemd-gfx/test-gl.c | 342 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 364 insertions(+)
create mode 100644 src/libsystemd-gfx/test-gl.c
diff --git a/.gitignore b/.gitignore
index a61f68d..c856412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,6 +116,7 @@
/test-event
/test-fileio
/test-gfx
+/test-gl
/test-hashmap
/test-hostname
/test-id128
diff --git a/Makefile.am b/Makefile.am
index aa17876..1e8aeed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3886,6 +3886,19 @@ test_gfx_LDADD = \
libsystemd-shared.la \
libsystemd-gfx.la
+test_gl_SOURCES = \
+ src/libsystemd-gfx/test-gl.c
+
+test_gl_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GFX_GL_CFLAGS)
+
+test_gl_LDADD = \
+ $(GFX_GL_LIBS) \
+ libsystemd-bus-internal.la \
+ libsystemd-shared.la \
+ libsystemd-gfx.la
+
test_kbd_SOURCES = \
src/libsystemd-gfx/test-kbd.c
@@ -3903,6 +3916,11 @@ tests += \
test-gfx \
test-kbd
+if HAVE_GFX_GL
+# Uncomment this to enable test-gl builds
+#tests += test-gl
+endif
Telling people to edit the makefile doesn't seem right. Maybe add a configure
swith a la bd441fa27a? Then the GFX_GL switch below could be changed to actually
error out if any of those modules are not found.
Post by David Herrmann
src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
src/libsystemd-gfx/unifont.bin: src/libsystemd-gfx/unifont.hex make-unifont.py
$(AM_V_at)$(MKDIR_P) $(dir $@)
$(AM_V_GEN)$(PYTHON) $+ >$@

... and make make-unitfont.py accept an arg.
mkdir -p is needed for out of tree builds.
Post by David Herrmann
+ r = sd_gfx_card_new(&card, "/dev/dri/card0", gl_fd, event);
Maybe 'argv[1] ?: "/dev/dri/card0"' for manual testing?

Zbyszek
David Herrmann
2013-12-01 09:48:32 UTC
Permalink
Hi

On Sun, Dec 1, 2013 at 6:21 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
The test-gl helper shows how sd_gfx_card can be used to get a full OpenGL
context on the device. It is not added to the build-tools as it requires
mesa and might break on Khronos header-updates (yes, they break API *and*
ABI compatibility often!).
---
.gitignore | 1 +
Makefile.am | 18 +++
configure.ac | 3 +
src/libsystemd-gfx/test-gl.c | 342 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 364 insertions(+)
create mode 100644 src/libsystemd-gfx/test-gl.c
diff --git a/.gitignore b/.gitignore
index a61f68d..c856412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,6 +116,7 @@
/test-event
/test-fileio
/test-gfx
+/test-gl
/test-hashmap
/test-hostname
/test-id128
diff --git a/Makefile.am b/Makefile.am
index aa17876..1e8aeed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3886,6 +3886,19 @@ test_gfx_LDADD = \
libsystemd-shared.la \
libsystemd-gfx.la
+test_gl_SOURCES = \
+ src/libsystemd-gfx/test-gl.c
+
+test_gl_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GFX_GL_CFLAGS)
+
+test_gl_LDADD = \
+ $(GFX_GL_LIBS) \
+ libsystemd-bus-internal.la \
+ libsystemd-shared.la \
+ libsystemd-gfx.la
+
test_kbd_SOURCES = \
src/libsystemd-gfx/test-kbd.c
@@ -3903,6 +3916,11 @@ tests += \
test-gfx \
test-kbd
+if HAVE_GFX_GL
+# Uncomment this to enable test-gl builds
+#tests += test-gl
+endif
Telling people to edit the makefile doesn't seem right. Maybe add a configure
swith a la bd441fa27a? Then the GFX_GL switch below could be changed to actually
error out if any of those modules are not found.
Yeah, I think I will remove it entirely instead. It was just a
proof-of-concept that you can use sd_gfx_card with OpenGL. Doesn't
make much sense to keep it.
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
src/libsystemd-gfx/unifont.bin: src/libsystemd-gfx/unifont.hex make-unifont.py
... and make make-unitfont.py accept an arg.
mkdir -p is needed for out of tree builds.
Hm, "make update-unifont" should only be used by maintainers to update
the hex-file. You actually need to download the file and rename it to
src/libsystemd-gfx/unifont.hex for this to make sense. So is there any
reason to support out-of-tree builds for that? Doesn't make sense to
me, as you only want to call it if you check the result into git.

Argument seems fine, I will try to fix it up (if I only knew python better..).
Post by Zbigniew Jędrzejewski-Szmek
Post by David Herrmann
+ r = sd_gfx_card_new(&card, "/dev/dri/card0", gl_fd, event);
Maybe 'argv[1] ?: "/dev/dri/card0"' for manual testing?
The other tests already use udev, I should fix this either up or
remove it, yepp.

Thanks for reviewing!
David
David Herrmann
2013-11-27 18:48:47 UTC
Permalink
systemd-consoled is a very basic terminal-emulator to replace the
in-kernel VT layer. It is based on libtsm as emulation layer (which itself
has no external dependencies).

systemd-consoled expects to be run in a logind-session. The caller must
have already setup the session and prepared it for systemd-consoled. This
is usually done by login-managers.
---
.gitignore | 1 +
Makefile.am | 24 +++
configure.ac | 17 ++
src/console/Makefile | 1 +
src/console/consoled-pty.c | 391 ++++++++++++++++++++++++++++++++++++++++
src/console/consoled-screen.c | 170 +++++++++++++++++
src/console/consoled-terminal.c | 371 ++++++++++++++++++++++++++++++++++++++
src/console/consoled.c | 278 ++++++++++++++++++++++++++++
src/console/consoled.h | 128 +++++++++++++
9 files changed, 1381 insertions(+)
create mode 120000 src/console/Makefile
create mode 100644 src/console/consoled-pty.c
create mode 100644 src/console/consoled-screen.c
create mode 100644 src/console/consoled-terminal.c
create mode 100644 src/console/consoled.c
create mode 100644 src/console/consoled.h

diff --git a/.gitignore b/.gitignore
index c856412..0f563c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@
/systemd-cgls
/systemd-cgroups-agent
/systemd-cgtop
+/systemd-consoled
/systemd-coredump
/systemd-coredumpctl
/systemd-cryptsetup
diff --git a/Makefile.am b/Makefile.am
index 1e8aeed..b5f1fd9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3937,6 +3937,30 @@ update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp
endif

# ------------------------------------------------------------------------------
+if ENABLE_CONSOLED
+rootlibexec_PROGRAMS += \
+ systemd-consoled
+
+systemd_consoled_SOURCES = \
+ src/console/consoled.h \
+ src/console/consoled.c \
+ src/console/consoled-pty.c \
+ src/console/consoled-screen.c \
+ src/console/consoled-terminal.c
+
+systemd_consoled_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CONSOLED_CFLAGS)
+
+systemd_consoled_LDADD = \
+ $(CONSOLED_LIBS) \
+ libsystemd-bus.la \
+ libsystemd-daemon.la \
+ libsystemd-gfx.la \
+ libsystemd-shared.la
+endif
+
+# ------------------------------------------------------------------------------
if ENABLE_NETWORKD
rootlibexec_PROGRAMS += \
systemd-networkd
diff --git a/configure.ac b/configure.ac
index bf3fce3..924637c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -306,6 +306,22 @@ AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
AM_CONDITIONAL(HAVE_GFX_GL, [test "$have_gfx_gl" = "yes"])

# ------------------------------------------------------------------------------
+have_consoled=no
+AC_ARG_ENABLE(consoled, AS_HELP_STRING([--disable-consoled], [disable system console]))
+if test "x$enable_consoled" != "xno"; then
+ if test "x$have_gfx" = xno -a "x$enable_consoled" = xyes; then
+ AC_MSG_ERROR([*** consoled support requested, but libraries not found])
+ elif test "x$have_gfx" = xyes ; then
+ PKG_CHECK_MODULES(CONSOLED, [ libtsm >= 3 ],
+ [AC_DEFINE(ENABLE_CONSOLED, 1, [Define if system console is built]) have_consoled=yes], have_consoled=no)
+ if test "x$have_consoled" = xno -a "x$enable_consoled" = xyes; then
+ AC_MSG_ERROR([*** consoled support requested, but libraries not found])
+ fi
+ fi
+fi
+AM_CONDITIONAL(ENABLE_CONSOLED, [test "$have_consoled" = "yes"])
+
+# ------------------------------------------------------------------------------
have_blkid=no
AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support]))
if test "x$enable_blkid" != "xno"; then
@@ -1095,6 +1111,7 @@ AC_MSG_RESULT([
efi: ${have_efi}
kmod: ${have_kmod}
sd-gfx: ${have_gfx}
+ consoled: ${have_consoled}
blkid: ${have_blkid}
nss-myhostname: ${have_myhostname}
gudev: ${enable_gudev}
diff --git a/src/console/Makefile b/src/console/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/console/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/console/consoled-pty.c b/src/console/consoled-pty.c
new file mode 100644
index 0000000..c3defc1
--- /dev/null
+++ b/src/console/consoled-pty.c
@@ -0,0 +1,391 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pty.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "consoled.h"
+#include "ring.h"
+#include "sd-event.h"
+
+/*
+ * PTY
+ * A PTY object represents a single PTY connection between a master and a
+ * child. The child process is fork()ed so the caller controls what program
+ * will be run.
+ *
+ * Programs like /bin/login tend to perform a vhangup() on their TTY
+ * before running the login procedure. This also causes the pty master
+ * to get a EPOLLHUP event as long as no client has the TTY opened.
+ * This means, we cannot use the TTY connection as reliable way to track
+ * the client. Instead, we _must_ rely on the PID of the client to track
+ * them.
+ * However, this has the side effect that if the client forks and the
+ * parent exits, we loose them and restart the client. But this seems to
+ * be the expected behavior so we implement it here.
+ *
+ * Unfortunately, epoll always polls for EPOLLHUP so as long as the
+ * vhangup() is ongoing, we will _always_ get EPOLLHUP and cannot sleep.
+ * This gets worse if the client closes the TTY but doesn't exit.
+ * Therefore, the fd must be edge-triggered in the epoll-set so we
+ * only get the events once they change.
+ */
+
+static void pty_dispatch_write(Pty *pty) {
+ struct iovec vec[2];
+ size_t num;
+ ssize_t r;
+
+ num = ring_peek(&pty->out_buf, vec);
+ if (!num)
+ return;
+
+ /* ignore errors in favor of SIGCHLD; (we're edge-triggered, anyway) */
+ r = writev(pty->fd, vec, (int)num);
+ if (r > 0)
+ ring_pull(&pty->out_buf, (size_t)r);
+}
+
+static int pty_dispatch_read(Pty *pty) {
+ ssize_t len, num;
+
+ /* We're edge-triggered, means we need to read the whole queue. This,
+ * however, might cause us to stall if the writer is faster than we
+ * are. Therefore, we have some rather arbitrary limit on how fast
+ * we read. If we reach it, we simply return EAGAIN to the caller and
+ * let them schedule an idle-event. */
+
+ num = 4;
+ do {
+ len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf));
+ if (len > 0)
+ terminal_from_pty(pty->t, pty->in_buf, len);
+ } while (len > 0 && --num);
+
+ return !num ? -EAGAIN : 0;
+}
+
+static int pty_dispatch(Pty *pty) {
+ int r;
+
+ r = pty_dispatch_read(pty);
+ pty_dispatch_write(pty);
+ return r;
+}
+
+static int pty_idle_fn(sd_event_source *source, void *data) {
+ Pty *pty = data;
+ int r;
+
+ r = pty_dispatch(pty);
+ if (r == -EAGAIN)
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+
+ return 0;
+}
+
+static int pty_io_fn(sd_event_source *source, int fd, uint32_t ev, void *data) {
+ Pty *pty = data;
+ int r;
+
+ r = pty_dispatch(pty);
+ if (r == -EAGAIN)
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+
+ return 0;
+}
+
+enum {
+ PTY_FAILED,
+ PTY_SETUP,
+};
+
+static char pty_wait(int fd) {
+ int r;
+ char d;
+
+ do {
+ r = read(fd, &d, 1);
+ } while (r < 0 && (errno == EINTR || errno == EAGAIN));
+
+ return (r <= 0) ? PTY_FAILED : d;
+}
+
+static int pty_wakeup(int fd, char d) {
+ int r;
+
+ do {
+ r = write(fd, &d, 1);
+ } while (r < 0 && (errno == EINTR || errno == EAGAIN));
+
+ return (r == 1) ? 0 : -EINVAL;
+}
+
+static int pty_setup_child(int slave, unsigned short term_width, unsigned short term_height) {
+ struct termios attr;
+ struct winsize ws;
+
+ if (tcgetattr(slave, &attr) < 0)
+ return -errno;
+
+ /* erase character should be normal backspace, PLEASEEE! */
+ attr.c_cc[VERASE] = 010;
+
+ if (tcsetattr(slave, TCSANOW, &attr) < 0)
+ return -errno;
+
+ memset(&ws, 0, sizeof(ws));
+ ws.ws_col = term_width;
+ ws.ws_row = term_height;
+
+ if (ioctl(slave, TIOCSWINSZ, &ws) < 0)
+ return -errno;
+
+ if (dup2(slave, STDIN_FILENO) != STDIN_FILENO ||
+ dup2(slave, STDOUT_FILENO) != STDOUT_FILENO ||
+ dup2(slave, STDERR_FILENO) != STDERR_FILENO)
+ return -errno;
+
+ return 0;
+}
+
+static int pty_init_child(int fd) {
+ int r;
+ sigset_t sigset;
+ char *slave_name;
+ int slave, i;
+ pid_t pid;
+
+ /* unlockpt() requires unset signal-handlers */
+ sigemptyset(&sigset);
+ r = sigprocmask(SIG_SETMASK, &sigset, NULL);
+ if (r < 0)
+ return -errno;
+
+ for (i = 1; i < SIGUNUSED; ++i)
+ signal(i, SIG_DFL);
+
+ r = grantpt(fd);
+ if (r < 0)
+ return -errno;
+
+ r = unlockpt(fd);
+ if (r < 0)
+ return -errno;
+
+ slave_name = ptsname(fd);
+ if (!slave_name)
+ return -errno;
+
+ /* open slave-TTY */
+ slave = open(slave_name, O_RDWR | O_CLOEXEC | O_NOCTTY);
+ if (slave < 0)
+ return -errno;
+
+ /* open session so we loose our controlling TTY */
+ pid = setsid();
+ if (pid < 0) {
+ close(slave);
+ return -errno;
+ }
+
+ /* set controlling TTY */
+ r = ioctl(slave, TIOCSCTTY, 0);
+ if (r < 0) {
+ close(slave);
+ return -errno;
+ }
+
+ return slave;
+}
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out) {
+ Pty *pty;
+ pid_t pid;
+ int fd, comm[2], slave, r;
+ char d;
+
+ pty = calloc(1, sizeof(*pty));
+ if (!pty)
+ return -ENOMEM;
+
+ fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0) {
+ free(pty);
+ return -errno;
+ }
+
+ r = sd_event_add_io(t->m->event,
+ fd,
+ EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET,
+ pty_io_fn,
+ pty,
+ &pty->fd_source);
+ if (r < 0) {
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = sd_event_add_defer(t->m->event, pty_idle_fn, pty, &pty->idle_source);
+ if (r < 0) {
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = pipe2(comm, O_CLOEXEC);
+ if (r < 0) {
+ r = -errno;
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ /* error */
+ pid = -errno;
+ close(comm[0]);
+ close(comm[1]);
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return pid;
+ } else if (!pid) {
+ /* child */
+ close(comm[0]);
+ free(pty);
+
+ slave = pty_init_child(fd);
+ close(fd);
+
+ if (slave < 0)
+ _exit(1);
+
+ r = pty_setup_child(slave, term_width, term_height);
+ if (r < 0)
+ _exit(1);
+
+ /* close slave if it's not one of the std-fds */
+ if (slave > 2)
+ close(slave);
+
+ /* wake parent */
+ pty_wakeup(comm[1], PTY_SETUP);
+ close(comm[1]);
+
+ *out = NULL;
+ return pid;
+ }
+
+ /* parent */
+ close(comm[1]);
+
+ pty->fd = fd;
+ pty->child = pid;
+ pty->t = t;
+
+ /* Wait for child setup. We need to do that to guarantee that any
+ * following signals are really delivered. Maybe this is too pedantic,
+ * but.. it's already implemented. */
+ d = pty_wait(comm[0]);
+ if (d != PTY_SETUP) {
+ close(comm[0]);
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return -EINVAL;
+ }
+
+ close(comm[0]);
+ *out = pty;
+ return pid;
+}
+
+void pty_free(Pty *pty) {
+ if (!pty)
+ return;
+
+ close(pty->fd);
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ ring_clear(&pty->out_buf);
+ free(pty);
+}
+
+int pty_write(Pty *pty, const char *u8, size_t len) {
+ int r;
+
+ r = ring_push(&pty->out_buf, u8, len);
+ if (r < 0)
+ return r;
+
+ return sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+}
+
+int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height) {
+ struct winsize ws;
+ int r;
+
+ memset(&ws, 0, sizeof(ws));
+ ws.ws_col = term_width;
+ ws.ws_row = term_height;
+
+ /* This will send SIGWINCH to the pty slave foreground process group.
+ * We will also get one, but we don't need it. */
+ r = ioctl(pty->fd, TIOCSWINSZ, &ws);
+ return (r < 0) ? -errno : 0;
+}
+
+int pty_signal(Pty *pty, int sig) {
+ int r;
+
+ r = ioctl(pty->fd, TIOCSIG, sig);
+ return (r < 0) ? -errno : 0;
+}
diff --git a/src/console/consoled-screen.c b/src/console/consoled-screen.c
new file mode 100644
index 0000000..00c26d3
--- /dev/null
+++ b/src/console/consoled-screen.c
@@ -0,0 +1,170 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "list.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out) {
+ Screen *s;
+
+ s = calloc(1, sizeof(*s));
+ if (!s)
+ return log_oom();
+
+ s->t = t;
+ s->pipe = pipe;
+ s->plane = sd_gfx_pipe_get_primary_plane(pipe);
+
+ LIST_PREPEND(screen, t->screens, s);
+ *out = s;
+ return 0;
+}
+
+void screen_free(Screen *s) {
+ LIST_REMOVE(screen, s->t->screens, s);
+ free(s);
+}
+
+struct screen_data {
+ Screen *s;
+ sd_gfx_fb *fb;
+};
+
+static int screen_render_cell_fn(struct tsm_screen *screen,
+ uint32_t id,
+ const uint32_t *ch,
+ size_t len,
+ unsigned int cwidth,
+ unsigned int posx,
+ unsigned int posy,
+ const struct tsm_screen_attr *attr,
+ tsm_age_t age,
+ void *data) {
+ struct screen_data *d = data;
+ Screen *s = d->s;
+ sd_gfx_fb *fb = d->fb;
+ sd_gfx_buffer *buf;
+ unsigned int x, y;
+ uint32_t fc, bc, tc;
+
+ x = posx * s->t->cell_width;
+ y = posy * s->t->cell_height;
+
+ fc = (0xff << 24) | (attr->fr << 16) | (attr->fg << 8) | attr->fb;
+ bc = (0xff << 24) | (attr->br << 16) | (attr->bg << 8) | attr->bb;
+ if (attr->inverse) {
+ tc = fc;
+ fc = bc;
+ bc = tc;
+ }
+
+ if (!len) {
+ sd_gfx_fb_fill(fb, bc, x, y, s->t->cell_width, s->t->cell_height);
+ } else {
+ sd_gfx_font_render(s->t->font, id, ch, len, &buf);
+ sd_gfx_fb_blend_bichrome(fb, fc, bc, x, y, buf);
+ }
+
+ return 0;
+}
+
+static void screen_render(Screen *s, sd_gfx_fb *fb) {
+ struct screen_data d;
+
+ d.s = s;
+ d.fb = fb;
+ tsm_screen_draw(s->t->screen, screen_render_cell_fn, (void*)&d);
+}
+
+void screen_redraw(Screen *s) {
+ sd_gfx_fb *fb, *front = NULL;
+ int r;
+
+ if (!s->active)
+ return;
+
+ if (s->swapping) {
+ s->need_redraw = 1;
+ return;
+ }
+
+ fb = sd_gfx_plane_get_back(s->plane);
+ if (!fb) {
+ front = sd_gfx_plane_get_front(s->plane);
+ fb = front;
+ }
+
+ s->need_redraw = 0;
+ screen_render(s, fb);
+
+ if (fb == front)
+ return;
+
+ sd_gfx_plane_swap_to(s->plane, fb);
+ r = sd_gfx_pipe_commit(s->pipe);
+ if (r < 0) {
+ log_error("screen: cannot swap primary plane: %d", r);
+ } else {
+ s->swapping = 1;
+ }
+}
+
+void screen_wake_up(Screen *s) {
+ sd_gfx_fb *fb;
+
+ fb = sd_gfx_plane_get_front(s->plane);
+ s->width = sd_gfx_fb_get_width(fb);
+ s->height = sd_gfx_fb_get_height(fb);
+ s->active = 1;
+}
+
+void screen_sleep(Screen *s) {
+ s->width = 0;
+ s->height = 0;
+ s->active = 0;
+}
+
+void screen_swap(Screen *s) {
+ if (!s->swapping)
+ return;
+
+ s->swapping = 0;
+ if (s->need_redraw)
+ screen_redraw(s);
+}
diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c
new file mode 100644
index 0000000..963266d
--- /dev/null
+++ b/src/console/consoled-terminal.c
@@ -0,0 +1,371 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libtsm.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+static void terminal_tsm_log_fn(void *data,
+ const char *file,
+ int line,
+ const char *fn,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args) {
+ char *msg;
+ int r;
+
+ r = vasprintf(&msg, format, args);
+ if (r < 0)
+ return;
+
+ log_meta(sev, file, line, fn, "%s: %s", subs, msg);
+
+ free(msg);
+}
+
+static void terminal_tsm_write_fn(struct tsm_vte *vte,
+ const char *u8,
+ size_t len,
+ void *data) {
+ Terminal *t = data;
+ int r;
+
+ if (!t->pty)
+ return;
+
+ r = pty_write(t->pty, u8, len);
+ if (r < 0)
+ log_error("cannot write message to PTY: %d", r);
+}
+
+int terminal_new(Manager *m, Terminal **out) {
+ Terminal *t;
+ int r;
+
+ t = calloc(1, sizeof(*t));
+ if (!t)
+ return log_oom();
+
+ t->m = m;
+
+ r = sd_gfx_font_new(&t->font, 90);
+ if (r < 0)
+ goto error;
+
+ r = tsm_screen_new(&t->screen, terminal_tsm_log_fn, NULL);
+ if (r < 0) {
+ log_error("cannot allocate TSM screen: %d", r);
+ goto error;
+ }
+
+ r = tsm_vte_new(&t->vte,
+ t->screen,
+ terminal_tsm_write_fn,
+ t,
+ terminal_tsm_log_fn,
+ NULL);
+ if (r < 0) {
+ log_error("cannot allocate TSM VTE: %d", r);
+ goto error;
+ }
+
+ *out = t;
+ return 0;
+
+error:
+ tsm_vte_unref(t->vte);
+ tsm_screen_unref(t->screen);
+ sd_gfx_font_free(t->font);
+ free(t);
+ return r;
+}
+
+void terminal_free(Terminal *t) {
+ Screen *s;
+
+ if (!t)
+ return;
+
+ if (t->pty) {
+ sd_event_source_set_enabled(t->pty_source, SD_EVENT_OFF);
+ sd_event_source_unref(t->pty_source);
+ pty_signal(t->pty, SIGHUP);
+ pty_free(t->pty);
+ t->pty = NULL;
+ }
+
+ while ((s = t->screens))
+ screen_free(s);
+
+ tsm_vte_unref(t->vte);
+ tsm_screen_unref(t->screen);
+ sd_gfx_font_free(t->font);
+ free(t);
+}
+
+static void _noreturn_ terminal_run_child(Terminal *t) {
+ char **argv = (char**)(const char*[]) {
+ getenv("SHELL") ? : _PATH_BSHELL,
+ "-il",
+ NULL
+ };
+
+ setenv("TERM", "xterm-256color", 1);
+ execve(argv[0], argv, environ);
+ _exit(1);
+}
+
+static int terminal_child_fn(sd_event_source *source, const siginfo_t *s, void *data) {
+ Terminal *t = data;
+
+ log_warning("child process died");
+
+ pty_signal(t->pty, SIGHUP);
+ pty_free(t->pty);
+ t->pty = NULL;
+ sd_event_request_quit(t->m->event);
+
+ return 0;
+}
+
+void terminal_start(Terminal *t) {
+ pid_t pid;
+ int r;
+
+ pid = pty_new(t->cols, t->rows, t, &t->pty);
+ if (pid < 0)
+ goto error;
+ else if (!pid)
+ terminal_run_child(t);
+
+ r = sd_event_add_child(t->m->event,
+ t->pty->child,
+ WEXITED,
+ terminal_child_fn,
+ t,
+ &t->pty_source);
+ if (r < 0)
+ goto error;
+
+ terminal_redraw(t);
+ return;
+
+error:
+ log_error("cannot spawn PTY: %d/%d", (int)pid, r);
+ if (t->pty) {
+ pty_signal(t->pty, SIGHUP);
+ pty_free(t->pty);
+ t->pty = NULL;
+ }
+ sd_event_request_quit(t->m->event);
+}
+
+void terminal_redraw(Terminal *t) {
+ Screen *s;
+
+ LIST_FOREACH(screen, s, t->screens)
+ screen_redraw(s);
+}
+
+static void terminal_resize(Terminal *t) {
+ int r;
+
+ t->cell_width = sd_gfx_font_get_width(t->font) ? : 1;
+ t->cols = t->min_width / t->cell_width;
+ if (!t->cols)
+ t->cols = 1;
+
+ t->cell_height = sd_gfx_font_get_height(t->font) ? : 1;
+ t->rows = t->min_height / t->cell_height;
+ if (!t->rows)
+ t->rows = 1;
+
+ log_debug("terminal: new size is %ux%u / %ux%u",
+ t->min_width, t->min_height,
+ t->cols, t->rows);
+
+ r = tsm_screen_resize(t->screen, t->cols, t->rows);
+ if (r < 0) {
+ log_error("cannot resize screen: %d", r);
+ } else if (t->pty) {
+ pty_resize(t->pty, t->cols, t->rows);
+ }
+}
+
+void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe) {
+ Screen *s;
+ int r;
+
+ r = screen_new(t, pipe, &s);
+ if (r < 0)
+ return;
+
+ sd_gfx_pipe_set_data(pipe, s);
+}
+
+void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe) {
+ Screen *s = sd_gfx_pipe_get_data(pipe);
+
+ if (!s)
+ return;
+
+ screen_free(s);
+}
+
+void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe) {
+ Screen *s = sd_gfx_pipe_get_data(pipe);
+ bool resize = false;
+
+ if (!s)
+ return;
+
+ screen_wake_up(s);
+
+ if (s->width < t->min_width || !t->min_width) {
+ t->min_width = s->width;
+ resize = true;
+ }
+ if (s->height < t->min_height || !t->min_height) {
+ t->min_height = s->height;
+ resize = true;
+ }
+
+ if (resize) {
+ terminal_resize(t);
+ terminal_redraw(t);
+ } else {
+ screen_redraw(s);
+ }
+}
+
+void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe) {
+ Screen *s = sd_gfx_pipe_get_data(pipe);
+ unsigned int min_width, min_height;
+ bool resize = false;
+ Screen *i;
+
+ if (!s)
+ return;
+
+ if (s->width <= t->min_width || s->height <= t->min_height) {
+ min_width = 0;
+ min_height = 0;
+ LIST_FOREACH(screen, i, t->screens) {
+ if (!i->active)
+ continue;
+ if (i->width < min_width || !min_width)
+ min_width = i->width;
+ if (i->height < min_height || !min_height)
+ min_height = i->height;
+ }
+
+ if (min_width != t->min_width || min_height != t->min_height)
+ resize = true;
+ }
+
+ screen_sleep(s);
+
+ if (!resize)
+ return;
+
+ t->min_width = min_width;
+ t->min_height = min_height;
+ terminal_resize(t);
+ terminal_redraw(t);
+}
+
+void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe) {
+ Screen *s = sd_gfx_pipe_get_data(pipe);
+
+ if (!s)
+ return;
+
+ screen_swap(s);
+}
+
+static void terminal_kbd_fn(sd_gfx_kbd *kbd, void *fn_data, struct sd_gfx_kbd_event *ev) {
+ Terminal *t = fn_data;
+ unsigned int mods;
+ uint32_t ucs4;
+
+ if (ev->sym_count != 1)
+ return;
+
+ mods = 0;
+ if (ev->mods & SD_GFX_SHIFT)
+ mods |= TSM_SHIFT_MASK;
+ if (ev->mods & SD_GFX_CAPSL)
+ mods |= TSM_LOCK_MASK;
+ if (ev->mods & SD_GFX_CTRL)
+ mods |= TSM_CONTROL_MASK;
+ if (ev->mods & SD_GFX_ALT)
+ mods |= TSM_ALT_MASK;
+ if (ev->mods & SD_GFX_LOGO)
+ mods |= TSM_LOGO_MASK;
+
+ ucs4 = ev->codepoints[0];
+ if (ev->codepoints[0] == 0xffffffff)
+ ucs4 = TSM_VTE_INVALID;
+
+ if (tsm_vte_handle_keyboard(t->vte,
+ ev->keysyms[0],
+ ev->ascii,
+ mods,
+ ucs4)) {
+ tsm_screen_sb_reset(t->screen);
+ }
+}
+
+void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd) {
+ sd_gfx_kbd_set_event_fn(kbd, terminal_kbd_fn);
+ sd_gfx_kbd_set_fn_data(kbd, t);
+}
+
+void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd) {
+ sd_gfx_kbd_set_event_fn(kbd, NULL);
+ sd_gfx_kbd_set_fn_data(kbd, NULL);
+}
+
+void terminal_from_pty(Terminal *t, const char *u8, size_t len) {
+ tsm_vte_input(t->vte, u8, len);
+ terminal_redraw(t);
+}
diff --git a/src/console/consoled.c b/src/console/consoled.c
new file mode 100644
index 0000000..b42b9aa
--- /dev/null
+++ b/src/console/consoled.c
@@ -0,0 +1,278 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+static void manager_card_fn(sd_gfx_card *card, void *fn_data, sd_gfx_card_event *ev) {
+ Manager *m = fn_data;
+ Terminal *t;
+
+ switch (ev->type) {
+ case SD_GFX_CARD_PIPE_CREATE:
+ /* add all pipes to default terminal */
+ t = m->terminal;
+ terminal_pipe_add(t, ev->pipe);
+ sd_gfx_pipe_set_fn_data(ev->pipe, t);
+ break;
+ case SD_GFX_CARD_PIPE_DESTROY:
+ t = sd_gfx_pipe_get_fn_data(ev->pipe);
+ terminal_pipe_remove(t, ev->pipe);
+ break;
+ case SD_GFX_CARD_PIPE_WAKE_UP:
+ t = sd_gfx_pipe_get_fn_data(ev->pipe);
+ terminal_pipe_wake_up(t, ev->pipe);
+ break;
+ case SD_GFX_CARD_PIPE_SLEEP:
+ t = sd_gfx_pipe_get_fn_data(ev->pipe);
+ terminal_pipe_sleep(t, ev->pipe);
+ break;
+ case SD_GFX_CARD_PIPE_SWAP:
+ t = sd_gfx_pipe_get_fn_data(ev->pipe);
+ terminal_pipe_swap(t, ev->pipe);
+ break;
+ }
+}
+
+static void manager_add_card(Manager *m, sd_gfx_card *card) {
+ sd_gfx_card_set_event_fn(card, manager_card_fn);
+ sd_gfx_card_set_fn_data(card, m);
+}
+
+static void manager_event_fn(sd_gfx_monitor *mon, void *fn_data, sd_gfx_monitor_event *ev) {
+ Manager *m = fn_data;
+
+ switch (ev->type) {
+ case SD_GFX_MONITOR_RUN:
+ terminal_start(m->terminal);
+ break;
+ case SD_GFX_MONITOR_CREATE:
+ switch (ev->devtype) {
+ case SD_GFX_DEV_CARD:
+ manager_add_card(m, ev->card);
+ break;
+ case SD_GFX_DEV_KBD:
+ terminal_kbd_add(m->terminal, ev->kbd);
+ break;
+ }
+ break;
+ case SD_GFX_MONITOR_DESTROY:
+ switch (ev->devtype) {
+ case SD_GFX_DEV_CARD:
+ /* gets destroyed by gfx-monitor */
+ break;
+ case SD_GFX_DEV_KBD:
+ terminal_kbd_remove(m->terminal, ev->kbd);
+ break;
+ }
+ break;
+ }
+}
+
+static int manager_signal_fn(sd_event_source *s, const struct signalfd_siginfo *ssi, void *data) {
+ Manager *m = data;
+
+ log_notice("catched signal %d, exiting..", (int)ssi->ssi_signo);
+ sd_event_request_quit(m->event);
+
+ return 0;
+}
+
+static int manager_new(Manager **out) {
+ static const int sigs[] = {
+ SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0
+ };
+ unsigned int i;
+ sigset_t mask;
+ Manager *m;
+ int r;
+
+ m = calloc(1, sizeof(*m));
+ if (!m)
+ return log_oom();
+
+ r = sd_event_default(&m->event);
+ if (r < 0) {
+ log_error("cannot get default event-loop: %d", r);
+ goto error;
+ }
+
+ sigemptyset(&mask);
+ for (i = 0; sigs[i]; ++i) {
+ sigaddset(&mask, sigs[i]);
+ r = sd_event_add_signal(m->event,
+ sigs[i],
+ manager_signal_fn,
+ m,
+ &m->sigs[i]);
+ if (r < 0) {
+ log_error("cannot block signal %d: %d",
+ sigs[i], r);
+ goto error;
+ }
+ }
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+
+ r = sd_gfx_monitor_new(&m->mon,
+ SD_GFX_DEV_KBD | SD_GFX_DEV_CARD,
+ SD_GFX_MONITOR_DEFAULT,
+ m->event);
+ if (r < 0)
+ goto error;
+
+ sd_gfx_monitor_set_fn_data(m->mon, m);
+ sd_gfx_monitor_set_event_fn(m->mon, manager_event_fn);
+ sd_gfx_monitor_parse_cmdline(m->mon);
+
+ r = terminal_new(m, &m->terminal);
+ if (r < 0)
+ goto error;
+
+ *out = m;
+ return 0;
+
+error:
+ sd_gfx_monitor_free(m->mon);
+ for (i = 0; m->sigs[i]; ++i)
+ sd_event_source_unref(m->sigs[i]);
+ sd_event_unref(m->event);
+ free(m);
+ return r;
+}
+
+static void manager_free(Manager *m) {
+ unsigned int i;
+
+ if (!m)
+ return;
+
+ sd_gfx_monitor_free(m->mon);
+ terminal_free(m->terminal);
+ for (i = 0; m->sigs[i]; ++i)
+ sd_event_source_unref(m->sigs[i]);
+ sd_event_unref(m->event);
+ free(m);
+}
+
+static int manager_run(Manager *m) {
+ return sd_event_loop(m->event);
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] ...\n\n"
+ "System console with integrated terminal emulator.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ };
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+ int c;
+
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+ switch(c) {
+ case 'h':
+ return help();
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0;
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached("Unhandled option");
+ }
+ }
+
+ if (optind < argc) {
+ log_error("This program does not take arguments.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ Manager *m = NULL;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = parse_argv(argc, argv);
+ if (r < 0)
+ return EXIT_FAILURE;
+ if (r == 0)
+ return EXIT_SUCCESS;
+
+ r = manager_new(&m);
+ if (r < 0)
+ goto finish;
+
+ sd_notify(false, "READY=1\nSTATUS=Running...");
+
+ r = manager_run(m);
+
+finish:
+ sd_notify(false, "STATUS=Shutting down...");
+
+ if (m)
+ manager_free(m);
+
+ log_debug("exiting..");
+ return abs(r);
+}
diff --git a/src/console/consoled.h b/src/console/consoled.h
new file mode 100644
index 0000000..edc327d
--- /dev/null
+++ b/src/console/consoled.h
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <errno.h>
+#include <libtsm.h>
+#include <stdlib.h>
+
+#include "list.h"
+#include "ring.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+
+typedef struct Manager Manager;
+typedef struct Terminal Terminal;
+typedef struct Screen Screen;
+typedef struct Pty Pty;
+
+struct Manager {
+ sd_event *event;
+ sd_event_source *sigs[_NSIG];
+ sd_gfx_monitor *mon;
+
+ Terminal *terminal;
+};
+
+struct Terminal {
+ Manager *m;
+ sd_gfx_font *font;
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+
+ Pty *pty;
+ sd_event_source *pty_source;
+
+ unsigned int min_width;
+ unsigned int min_height;
+ unsigned int cols;
+ unsigned int rows;
+ unsigned int cell_width;
+ unsigned int cell_height;
+
+ LIST_HEAD(Screen, screens);
+};
+
+struct Screen {
+ Terminal *t;
+ sd_gfx_pipe *pipe;
+ sd_gfx_plane *plane;
+
+ unsigned int width;
+ unsigned int height;
+
+ LIST_FIELDS(Screen, screen);
+
+ unsigned int active : 1;
+ unsigned int swapping : 1;
+ unsigned int need_redraw : 1;
+};
+
+#define PTY_BUFSIZE 16384
+
+struct Pty {
+ Terminal *t;
+ int fd;
+ sd_event_source *fd_source;
+ sd_event_source *idle_source;
+ pid_t child;
+ char in_buf[PTY_BUFSIZE];
+ Ring out_buf;
+};
+
+/* terminal */
+
+int terminal_new(Manager *m, Terminal **out);
+void terminal_free(Terminal *t);
+
+void terminal_start(Terminal *t);
+void terminal_redraw(Terminal *t);
+
+void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe);
+
+void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd);
+void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd);
+
+void terminal_from_pty(Terminal *t, const char *u8, size_t len);
+
+/* screen */
+
+int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out);
+void screen_free(Screen *s);
+
+void screen_redraw(Screen *s);
+void screen_wake_up(Screen *s);
+void screen_sleep(Screen *s);
+void screen_swap(Screen *s);
+
+/* pty */
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out);
+void pty_free(Pty *pty);
+
+int pty_write(Pty *pty, const char *u8, size_t len);
+int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height);
+int pty_signal(Pty *pty, int sig);
--
1.8.4.2
Lennart Poettering
2013-11-27 22:39:40 UTC
Permalink
Post by David Herrmann
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out) {
+ Pty *pty;
+ pid_t pid;
+ int fd, comm[2], slave, r;
+ char d;
+
+ pty = calloc(1, sizeof(*pty));
+ if (!pty)
+ return -ENOMEM;
+
+ fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0) {
+ free(pty);
+ return -errno;
+ }
+
+ r = sd_event_add_io(t->m->event,
+ fd,
+ EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET,
+ pty_io_fn,
+ pty,
+ &pty->fd_source);
+ if (r < 0) {
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = sd_event_add_defer(t->m->event, pty_idle_fn, pty, &pty->idle_source);
+ if (r < 0) {
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = pipe2(comm, O_CLOEXEC);
+ if (r < 0) {
+ r = -errno;
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ /* error */
+ pid = -errno;
+ close(comm[0]);
+ close(comm[1]);
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return pid;
Grr. Just define a label to jump to to clean everything up that is
initialized, and skip over the bits that isn't. Duplicating the
destruction logic on every if block is just wrong...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:42:41 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:39 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out) {
+ Pty *pty;
+ pid_t pid;
+ int fd, comm[2], slave, r;
+ char d;
+
+ pty = calloc(1, sizeof(*pty));
+ if (!pty)
+ return -ENOMEM;
+
+ fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0) {
+ free(pty);
+ return -errno;
+ }
+
+ r = sd_event_add_io(t->m->event,
+ fd,
+ EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET,
+ pty_io_fn,
+ pty,
+ &pty->fd_source);
+ if (r < 0) {
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = sd_event_add_defer(t->m->event, pty_idle_fn, pty, &pty->idle_source);
+ if (r < 0) {
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ r = pipe2(comm, O_CLOEXEC);
+ if (r < 0) {
+ r = -errno;
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return r;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ /* error */
+ pid = -errno;
+ close(comm[0]);
+ close(comm[1]);
+ sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->idle_source);
+ sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(pty->fd_source);
+ close(fd);
+ free(pty);
+ return pid;
Grr. Just define a label to jump to to clean everything up that is
initialized, and skip over the bits that isn't. Duplicating the
destruction logic on every if block is just wrong...
Closed-source developers get paid by number of lines, why don't get
open-source developers honored by number of lines? I would excel in
that category!

Fixed.

Thanks
David
David Herrmann
2013-11-27 18:48:43 UTC
Permalink
While all previous sd-gfx interfaces are self-contained and can be used
directly on selected devices, this adds an interface to connect them all
together. The sd_gfx_monitor can be used to monitor a session for device
hotplugging and other device events. It is optional but is supposed to be
the foundation of all systemd-helpers that use sd-gfx.

The main function of sd_gfx_monitor is to watch the system for udev-events
of gpus and keyboards. For each of them, an sd_gfx_card or sd_gfx_kbd
device is created and advertised to the application. Furthermore,
systemd-localed integration is provided so keymap changes are immediately
noticed and applied to active sd_gfx_kbd devices.

An sd_gfx_monitor can run in two modes:
- system mode: In system mode, no dbus, no logind and localed are assumed
to be running and seat-information is ignored. This mode
allows to run applications in initrds or emergency
situations. It simply takes all devices it can find and
tries to use them. However, this obviously requires to be
root, otherwise, devices cannot be accessed.
- session mode: In session mode, the monitor assumes to be running in an
logind session. If not, it returns an error. The monitor
will call TakeControl on the active session and get
device-access via logind. Only devices attached to the
session will be used and no you're not required to be
root. The caller is responsible of setting up the session
before spawning the monitor.

Note that monitor setup is a blocking call as it is usually called during
application setup (and making that async would have no gain). But at
runtime, the monitor runs all dbus-calls and other calls asynchronously.

The sd_gfx_monitor interface is designed for fallbacks and basic system
applications. It does not allow per-device configurations or any advanced
eye-candy. It is trimmed for usability and simplicity, and it is optimized
for fallback/emergency situations. Thus, the monitor provides some basic
configuration options via the kernel command-line. systemd.gpus= allows to
filter the GPUs to be used (by default, *all* connected GPUs are used
together). systemd.keymap= allows to change the keymap in case localed is
configured incorrectly.

The sd_gfx_monitor interfaces has the nice side-effect that all
applications using it will share the same configuration. They will have
the same monitor-setup, the same keymap setup and use the same devices. So
if you system-boot fails, you can set systemd.XY="" to boot with a working
configuration and all systemd-internal applications will just work.

If we ever export sd-gfx, most users probably want more configurability
(like per-device keymaps) and thus will not use sd_gfx_monitor. However,
for all fallbacks, it is the perfect base.
---
Makefile.am | 3 +
src/libsystemd-gfx/gfx-monitor.c | 1767 ++++++++++++++++++++++++++++++++++++++
src/systemd/sd-gfx.h | 61 ++
3 files changed, 1831 insertions(+)
create mode 100644 src/libsystemd-gfx/gfx-monitor.c

diff --git a/Makefile.am b/Makefile.am
index 70148c5..189cc89 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3861,6 +3861,7 @@ libsystemd_gfx_la_SOURCES = \
src/libsystemd-gfx/sd-gfx.h \
src/libsystemd-gfx/gfx-drm.c \
src/libsystemd-gfx/gfx-kbd.c \
+ src/libsystemd-gfx/gfx-monitor.c \
src/libsystemd-gfx/gfx-unifont.c

libsystemd_gfx_la_CFLAGS = \
@@ -3872,7 +3873,9 @@ libsystemd_gfx_la_LIBADD = \
libsystemd-bus-internal.la \
libsystemd-daemon-internal.la \
libsystemd-id128-internal.la \
+ libsystemd-login-internal.la \
libsystemd-shared.la \
+ libudev-internal.la \
src/libsystemd-gfx/unifont.bin.lo

src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
diff --git a/src/libsystemd-gfx/gfx-monitor.c b/src/libsystemd-gfx/gfx-monitor.c
new file mode 100644
index 0000000..0fdf230
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-monitor.c
@@ -0,0 +1,1767 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libudev.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "bus-error.h"
+#include "bus-util.h"
+#include "def.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "sd-login.h"
+#include "strv.h"
+#include "util.h"
+
+typedef struct gfx_device gfx_device;
+
+struct gfx_device {
+ sd_gfx_monitor *mon;
+ struct udev_device *dev;
+ unsigned int devtype;
+
+ dev_t devt;
+ uint64_t logind_open_req;
+
+ unsigned int public : 1;
+
+ union {
+ sd_gfx_card *card;
+ sd_gfx_kbd *kbd;
+ };
+};
+
+struct sd_gfx_monitor {
+ unsigned int devmask;
+ unsigned int flags;
+ sd_event *event;
+ sd_bus *bus;
+ sd_gfx_monitor_event_fn event_fn;
+ void *fn_data;
+ char *sid;
+ char *seat;
+ char *spath;
+ unsigned int vt;
+
+ struct udev *udev;
+ struct udev_monitor *umon;
+ sd_event_source *umon_source;
+
+ Hashmap *devices;
+ Hashmap *devices_by_devt;
+ struct xkb_context *xkb;
+ struct xkb_keymap *keymap;
+
+ char **conf_gpus;
+ char *xkb_model;
+ char *xkb_layout;
+ char *xkb_variant;
+ char *xkb_options;
+ uint64_t localed_req;
+
+ unsigned int logind_active;
+ uint64_t logind_active_req;
+
+ unsigned int seat0 : 1;
+ unsigned int conf_gpus_boot : 1;
+ unsigned int conf_gpus_aux : 1;
+ unsigned int active : 1;
+};
+
+static int gfx_logind_take_device(gfx_device *dev);
+
+static unsigned devt_hash_func(const void *p) {
+ uint64_t u = *(const dev_t*)p;
+
+ return uint64_hash_func(&u);
+}
+
+static int devt_compare_func(const void *_a, const void *_b) {
+ dev_t a, b;
+
+ a = *(const dev_t*) _a;
+ b = *(const dev_t*) _b;
+
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+/*
+ * Device Detection
+ */
+
+static bool gfx_match_seat(struct udev_device *d, const char *want_seat, const char *s) {
+ const char *seat;
+
+ /* NULL means wild-card match */
+ if (!want_seat)
+ return true;
+
+ seat = udev_device_get_property_value(d, "ID_SEAT");
+ if (!seat)
+ seat = "seat0";
+
+ if (!streq(seat, want_seat)) {
+ log_debug("gfx: no seat match, ignore device %s", s);
+ return false;
+ }
+
+ return true;
+}
+
+static bool gfx_match_keyboard(struct udev_device *d, const char *s) {
+ const char *v;
+
+ /* We should really map against ID_INPUT_KEYBOARD instead. But to
+ * provide compatibility to the kernel VT layer, we bind to all
+ * EV_KEY devices here. */
+ v = udev_device_get_property_value(d, "ID_INPUT_KEY");
+ if (streq_ptr(v, "1"))
+ return true;
+
+ log_debug("gfx: ignore non-keyboard device %s", s);
+ return false;
+}
+
+static bool gfx_device_match_gpu(gfx_device *dev) {
+ const char *drv = NULL, *boot = NULL, *id_path = NULL;
+ const char *id_path_tag = NULL, *sname, *c;
+ struct udev_device *p;
+ unsigned int i;
+ sd_gfx_monitor *mon = dev->mon;
+
+ if (!mon->conf_gpus)
+ return true;
+
+ sname = udev_device_get_sysname(dev->dev);
+ id_path = udev_device_get_property_value(dev->dev, "ID_PATH");
+ id_path_tag = udev_device_get_property_value(dev->dev, "ID_PATH_TAG");
+
+ p = udev_device_get_parent(dev->dev);
+ if (p) {
+ drv = udev_device_get_driver(p);
+ boot = udev_device_get_sysattr_value(p, "boot_vga");
+ }
+
+ if (mon->conf_gpus_boot && streq_ptr(boot, "1"))
+ return true;
+
+ if (mon->conf_gpus_aux && drv) {
+ if (streq(drv, "udl"))
+ return true;
+ }
+
+ for (i = 0; mon->conf_gpus[i]; ++i) {
+ c = mon->conf_gpus[i];
+
+ /* match driver */
+ if (streq_ptr(c, drv))
+ return true;
+
+ /* match ID_PATH[_TAG] */
+ if (streq_ptr(c, id_path) || streq_ptr(c, id_path_tag))
+ return true;
+
+ /* match sysname */
+ if (streq_ptr(c, sname))
+ return true;
+ }
+
+ return false;
+}
+
+static unsigned int gfx_device_detect(gfx_device *dev) {
+ const char *s, *sub, *sname, *node;
+ struct udev_device *p;
+
+ s = udev_device_get_syspath(dev->dev);
+ sub = udev_device_get_subsystem(dev->dev);
+ sname = udev_device_get_sysname(dev->dev);
+ node = udev_device_get_devnode(dev->dev);
+ if (!sub || !sname || !node)
+ goto unknown;
+
+ if (streq(sub, "drm")) {
+ if (!gfx_match_seat(dev->dev, dev->mon->seat, s)) {
+ goto out;
+ } else if (strchr(node, '-') || !startswith(sname, "card")) {
+ log_debug("gfx: ignore secondary DRM node %s", s);
+ goto out;
+ } else if (!gfx_device_match_gpu(dev)) {
+ log_debug("gfx: no GPU match, ignore DRM device %s", s);
+ goto out;
+ }
+
+ return SD_GFX_DEV_CARD;
+ } else if (streq(sub, "input")) {
+ if (!startswith(sname, "event")) {
+ log_debug("gfx: ignore non-evdev input device %s", s);
+ goto out;
+ }
+
+ p = udev_device_get_parent_with_subsystem_devtype(dev->dev, "input", NULL);
+ if (!p)
+ goto unknown;
+
+ if (!gfx_match_seat(p, dev->mon->seat, s))
+ goto out;
+ if (!gfx_match_keyboard(p, s))
+ goto out;
+
+ return SD_GFX_DEV_KBD;
+ }
+
+unknown:
+ log_debug("gfx: ignore unknown device %s", s);
+out:
+ return SD_GFX_DEV_UNKNOWN;
+}
+
+/*
+ * Card Devices
+ */
+
+static void gfx_device_notify_card(gfx_device *dev, unsigned int type) {
+ sd_gfx_monitor_event ev;
+
+ if (!dev->public)
+ return;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = type;
+ ev.devtype = SD_GFX_DEV_CARD;
+ ev.card = dev->card;
+ if (dev->mon->event_fn)
+ dev->mon->event_fn(dev->mon, dev->mon->fn_data, &ev);
+}
+
+static void gfx_device_resume_card(gfx_device *dev, int fd) {
+ /* we don't care for new fds for DRM devices; keep the old one */
+ if (fd > 0)
+ close_nointr(fd);
+ sd_gfx_card_wake_up(dev->card);
+}
+
+static void gfx_device_pause_card(gfx_device *dev) {
+ sd_gfx_card_sleep(dev->card);
+}
+
+static int gfx_device_create_card(gfx_device *dev, int fd, bool paused) {
+ int r;
+
+ if (dev->mon->flags & SD_GFX_MONITOR_IGNORE_SEATS) {
+ /* if we run un-seated, make sure we're master */
+ r = drmSetMaster(fd);
+ if (r < 0) {
+ log_error("gfx: cannot become DRM-Master on %s: %m",
+ udev_device_get_syspath(dev->dev));
+ goto error;
+ }
+ }
+
+ r = sd_gfx_card_new(&dev->card,
+ udev_device_get_sysname(dev->dev),
+ fd,
+ dev->mon->event);
+ if (r < 0)
+ goto error;
+
+ dev->public = 1;
+ gfx_device_notify_card(dev, SD_GFX_MONITOR_CREATE);
+
+ if (!paused)
+ gfx_device_resume_card(dev, -1);
+
+ return 0;
+
+error:
+ close_nointr(fd);
+ return r;
+}
+
+static int gfx_device_new_card(gfx_device *dev) {
+ int r, fd;
+ const char *node;
+
+ if (dev->mon->sid) {
+ r = gfx_logind_take_device(dev);
+ if (r < 0)
+ return r;
+ } else {
+ node = udev_device_get_devnode(dev->dev);
+ fd = open(node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0) {
+ log_error("gfx: cannot open %s: %m", node);
+ return -errno;
+ }
+
+ r = gfx_device_create_card(dev, fd, false);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void gfx_device_free_card(gfx_device *dev) {
+ gfx_device_notify_card(dev, SD_GFX_MONITOR_DESTROY);
+
+ if (dev->card) {
+ /* If we run on a VT and our session is currently active *and* our
+ * device is currently awake, we should try to restore the old FB
+ * of the VT layer.
+ * Note that this actually puts the card asleep! */
+ if (dev->mon->vt && dev->mon->active && sd_gfx_card_is_awake(dev->card))
+ sd_gfx_card_restore(dev->card);
+
+ sd_gfx_card_sleep(dev->card);
+ sd_gfx_card_free(dev->card);
+ }
+}
+
+/*
+ * KBD Devices
+ */
+
+static void gfx_device_notify_kbd(gfx_device *dev, unsigned int type) {
+ sd_gfx_monitor_event ev;
+
+ if (!dev->public)
+ return;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = type;
+ ev.devtype = SD_GFX_DEV_KBD;
+ ev.kbd = dev->kbd;
+ if (dev->mon->event_fn)
+ dev->mon->event_fn(dev->mon, dev->mon->fn_data, &ev);
+}
+
+static void gfx_device_resume_kbd(gfx_device *dev, int fd) {
+ sd_gfx_kbd_wake_up(dev->kbd, fd);
+}
+
+static void gfx_device_pause_kbd(gfx_device *dev) {
+ sd_gfx_kbd_sleep(dev->kbd);
+}
+
+static int gfx_device_create_kbd(gfx_device *dev, int fd, bool paused) {
+ struct xkb_state *state = NULL;
+ int r;
+
+ state = xkb_state_new(dev->mon->keymap);
+ if (!state) {
+ r = log_oom();
+ goto error;
+ }
+
+ r = sd_gfx_kbd_new(&dev->kbd,
+ udev_device_get_sysname(dev->dev),
+ state,
+ dev->mon->event);
+ if (r < 0)
+ goto error;
+
+ dev->public = 1;
+ gfx_device_notify_kbd(dev, SD_GFX_MONITOR_CREATE);
+
+ if (!paused)
+ gfx_device_resume_kbd(dev, fd);
+ else
+ close_nointr(fd);
+
+ xkb_state_unref(state);
+ return 0;
+
+error:
+ xkb_state_unref(state);
+ close_nointr(fd);
+ return r;
+}
+
+static int gfx_device_new_kbd(gfx_device *dev) {
+ int r, fd = -1;
+ const char *node;
+
+ if (!dev->mon->keymap)
+ return -EINVAL;
+
+ if (dev->mon->sid) {
+ r = gfx_logind_take_device(dev);
+ if (r < 0)
+ return r;
+ } else {
+ node = udev_device_get_devnode(dev->dev);
+ fd = open(node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0) {
+ log_error("gfx: cannot open %s: %m", node);
+ return -errno;
+ }
+
+ r = gfx_device_create_kbd(dev, fd, false);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void gfx_device_free_kbd(gfx_device *dev) {
+ gfx_device_notify_kbd(dev, SD_GFX_MONITOR_DESTROY);
+ if (dev->kbd)
+ sd_gfx_kbd_free(dev->kbd);
+}
+
+/*
+ * Logind Integration
+ */
+
+static void gfx_logind_activate(sd_gfx_monitor *mon) {
+ if (mon->active)
+ return;
+
+ log_debug("gfx: session %s activated", mon->sid);
+ mon->active = 1;
+}
+
+static void gfx_logind_deactivate(sd_gfx_monitor *mon) {
+ if (!mon->active)
+ return;
+
+ log_debug("gfx: session %s deactivated", mon->sid);
+ mon->active = 0;
+}
+
+static void gfx_logind_resume(gfx_device *dev, int fd) {
+ log_debug("gfx: resume device %s",
+ udev_device_get_syspath(dev->dev));
+
+ switch (dev->devtype) {
+ case SD_GFX_DEV_CARD:
+ gfx_device_resume_card(dev, fd);
+ break;
+ case SD_GFX_DEV_KBD:
+ gfx_device_resume_kbd(dev, fd);
+ break;
+ default:
+ close_nointr(fd);
+ break;
+ }
+}
+
+static void gfx_logind_pause(gfx_device *dev) {
+ log_debug("gfx: pause device %s",
+ udev_device_get_syspath(dev->dev));
+
+ switch (dev->devtype) {
+ case SD_GFX_DEV_CARD:
+ gfx_device_pause_card(dev);
+ break;
+ case SD_GFX_DEV_KBD:
+ gfx_device_pause_kbd(dev);
+ break;
+ }
+}
+
+static void gfx_logind_open(gfx_device *dev, int fd, bool paused) {
+ sd_bus_message *req = NULL;
+ int r;
+
+ log_debug("gfx: open device %s",
+ udev_device_get_syspath(dev->dev));
+
+ switch (dev->devtype) {
+ case SD_GFX_DEV_CARD:
+ r = gfx_device_create_card(dev, fd, paused);
+ break;
+ case SD_GFX_DEV_KBD:
+ r = gfx_device_create_kbd(dev, fd, paused);
+ break;
+ default:
+ r = -EINVAL;
+ close_nointr(fd);
+ break;
+ }
+
+ if (r >= 0)
+ return;
+
+ r = sd_bus_message_new_method_call(dev->mon->bus,
+ "org.freedesktop.login1",
+ dev->mon->spath,
+ "org.freedesktop.login1.Session",
+ "ReleaseDevice",
+ &req);
+ if (r < 0)
+ return;
+
+ r = sd_bus_message_append(req, "uu", major(dev->devt), minor(dev->devt));
+ if (r >= 0)
+ sd_bus_send(dev->mon->bus, req, NULL);
+ sd_bus_message_unref(req);
+}
+
+static int gfx_logind_open_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ gfx_device *dev = data;
+ int r, fd = -1;
+ unsigned int paused;
+
+ dev->logind_open_req = 0;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &paused);
+ if (r < 0)
+ goto error;
+
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ goto error;
+
+ log_error("gfx: control over device %s granted",
+ udev_device_get_syspath(dev->dev));
+
+ gfx_logind_open(dev, fd, paused);
+ return 0;
+
+error:
+ log_error("gfx: systemd-logind disallowed access to %s: %d",
+ udev_device_get_syspath(dev->dev), r);
+ return 0;
+}
+
+static int gfx_logind_take_device(gfx_device *dev) {
+ sd_bus_message *req = NULL;
+ int r;
+
+ r = sd_bus_message_new_method_call(dev->mon->bus,
+ "org.freedesktop.login1",
+ dev->mon->spath,
+ "org.freedesktop.login1.Session",
+ "TakeDevice",
+ &req);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_append(req, "uu", major(dev->devt), minor(dev->devt));
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_call_async(dev->mon->bus, req, gfx_logind_open_fn,
+ dev, 0, &dev->logind_open_req);
+ if (r < 0)
+ goto error;
+
+ sd_bus_message_unref(req);
+
+ log_debug("gfx: taking control over device %s",
+ udev_device_get_syspath(dev->dev));
+ return 0;
+
+error:
+ log_error("gfx: cannot request device %s from logind: %d",
+ udev_device_get_syspath(dev->dev), r);
+ return r;
+}
+
+static int gfx_logind_resume_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r, fd;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
+
+ devt = makedev(major, minor);
+ dev = hashmap_get(mon->devices_by_devt, &devt);
+ if (dev) {
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ goto error;
+
+ gfx_logind_resume(dev, fd);
+ }
+
+ return 0;
+
+error:
+ log_error("gfx: cannot parse ResumeDevice signal: %d", r);
+ return 0;
+}
+
+static int gfx_logind_pause_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r;
+ const char *type;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &type);
+ if (r < 0)
+ goto error;
+
+ devt = makedev(major, minor);
+ dev = hashmap_get(mon->devices_by_devt, &devt);
+ if (dev)
+ gfx_logind_pause(dev);
+
+ return 0;
+
+error:
+ log_error("gfx: cannot parse PauseDevice signal: %d", r);
+ return 0;
+}
+
+static int gfx_logind_session_new_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ int r;
+ const char *sid;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &sid);
+ if (r < 0)
+ goto error;
+
+ if (strcmp(sid, mon->sid))
+ return 0;
+
+ /* TODO: ... */
+
+ return 0;
+
+error:
+ log_error("gfx: cannot parse SessionNew signal: %d", r);
+ return 0;
+}
+
+static int gfx_logind_session_removed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ int r;
+ const char *sid;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &sid);
+ if (r < 0)
+ goto error;
+
+ if (strcmp(sid, mon->sid))
+ return 0;
+
+ /* TODO: ... */
+
+ return 0;
+
+error:
+ log_error("gfx: cannot parse SessionRemoved signal: %d", r);
+ return 0;
+}
+
+static int gfx_logind_get_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ int r;
+
+ mon->logind_active_req = 0;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "b");
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &mon->logind_active);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ goto error;
+
+ if (mon->logind_active)
+ gfx_logind_activate(mon);
+ else
+ gfx_logind_deactivate(mon);
+
+ return 0;
+
+error:
+ log_error("gfx: cannot parse session information from logind: %d", r);
+ return 0;
+}
+
+static const struct bus_properties_map gfx_logind_map[] = {
+ { "Active", "b", NULL, offsetof(sd_gfx_monitor, logind_active) },
+ {}
+};
+
+static int gfx_logind_props_changed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ sd_bus_message *req = NULL;
+ int r;
+
+ sd_bus_call_async_cancel(bus, mon->logind_active_req);
+ mon->logind_active_req = 0;
+
+ r = bus_message_map_properties_changed(m, gfx_logind_map, mon);
+ if (r < 0)
+ goto error;
+
+ if (r > 0) {
+ r = sd_bus_message_new_method_call(bus,
+ "org.freedesktop.login1",
+ mon->spath,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ &req);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_append(req, "ss", "org.freedesktop.login1.Session", "Active");
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_call_async(bus, req, gfx_logind_get_fn,
+ mon, 0, &mon->logind_active_req);
+ if (r < 0)
+ goto error;
+
+ sd_bus_message_unref(req);
+ } else {
+ if (mon->logind_active)
+ gfx_logind_activate(mon);
+ else
+ gfx_logind_deactivate(mon);
+ }
+
+ return 0;
+
+error:
+ log_error("gfx: cannot request session information from logind: %d", r);
+ sd_bus_message_unref(req);
+ return 0;
+}
+
+static int gfx_logind_prepare_matches(sd_gfx_monitor *mon) {
+ char *v;
+ int r;
+
+ r = sd_bus_add_match(mon->bus,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Manager',"
+ "member='SessionNew',"
+ "path='/org/freedesktop/login1'",
+ gfx_logind_session_new_fn,
+ mon);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_add_match(mon->bus,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Manager',"
+ "member='SessionRemoved',"
+ "path='/org/freedesktop/login1'",
+ gfx_logind_session_removed_fn,
+ mon);
+ if (r < 0)
+ return r;
+
+ r = asprintf(&v,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Session',"
+ "member='PauseDevice',"
+ "path='%s'",
+ mon->spath);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = sd_bus_add_match(mon->bus, v, gfx_logind_pause_fn, mon);
+ free(v);
+ if (r < 0)
+ return r;
+
+ r = asprintf(&v,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Session',"
+ "member='ResumeDevice',"
+ "path='%s'",
+ mon->spath);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = sd_bus_add_match(mon->bus, v, gfx_logind_resume_fn, mon);
+ free(v);
+ if (r < 0)
+ return r;
+
+ r = asprintf(&v,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.DBus.Properties',"
+ "member='PropertiesChanged',"
+ "path='%s'",
+ mon->spath);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = sd_bus_add_match(mon->bus, v, gfx_logind_props_changed_fn, mon);
+ free(v);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int gfx_logind_prepare(sd_gfx_monitor *mon) {
+ _cleanup_free_ char *sid = NULL;
+ _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+ int r;
+
+ sid = sd_bus_label_escape(mon->sid);
+ if (!sid)
+ return log_oom();
+
+ r = asprintf(&mon->spath, "/org/freedesktop/login1/session/%s", sid);
+ if (r < 0)
+ goto error;
+
+ r = gfx_logind_prepare_matches(mon);
+ if (r < 0)
+ goto error;
+
+ r = bus_map_all_properties(mon->bus,
+ "org.freedesktop.login1",
+ mon->spath,
+ gfx_logind_map,
+ mon);
+ if (r < 0)
+ goto error;
+
+ mon->active = mon->logind_active;
+
+ r = sd_bus_call_method(mon->bus,
+ "org.freedesktop.login1",
+ mon->spath,
+ "org.freedesktop.login1.Session",
+ "TakeControl",
+ &err,
+ NULL,
+ "b", 0);
+ if (r < 0) {
+ log_error("gfx: cannot take control over session %s: %s",
+ mon->sid, bus_error_message(&err, -r));
+ return r;
+ }
+
+ log_debug("gfx: control over session %s granted", mon->sid);
+ return 0;
+
+error:
+ log_error("gfx: cannot setup systemd-logind bus connection: %d", r);
+ return r;
+}
+
+/*
+ * Devices
+ */
+
+static void gfx_device_new(sd_gfx_monitor *mon, struct udev_device *d) {
+ gfx_device *dev;
+ dev_t devt;
+ const char *s;
+ int r = 0;
+
+ s = udev_device_get_syspath(d);
+ if (!s || hashmap_get(mon->devices, s))
+ return;
+
+ devt = udev_device_get_devnum(d);
+ if (!devt || hashmap_get(mon->devices_by_devt, &devt))
+ return;
+
+ dev = calloc(1, sizeof(*dev));
+ if (!dev) {
+ r = log_oom();
+ goto err_out;
+ }
+
+ dev->mon = mon;
+ dev->dev = d;
+ dev->devt = devt;
+ dev->devtype = gfx_device_detect(dev);
+
+ if (!(mon->devmask & dev->devtype))
+ goto err_dev;
+
+ if (hashmap_put(mon->devices, s, dev) < 0 ||
+ hashmap_put(mon->devices_by_devt, &dev->devt, dev) < 0) {
+ r = log_oom();
+ goto err_dev;
+ }
+
+ switch (dev->devtype) {
+ case SD_GFX_DEV_CARD:
+ log_info("gfx: new DRM device %s", s);
+ r = gfx_device_new_card(dev);
+ if (r < 0)
+ goto err_dev;
+ break;
+ case SD_GFX_DEV_KBD:
+ log_info("gfx: new keyboard device %s", s);
+ r = gfx_device_new_kbd(dev);
+ if (r < 0)
+ goto err_dev;
+ break;
+ case SD_GFX_DEV_UNKNOWN:
+ default:
+ r = 0;
+ goto err_dev;
+ }
+
+ udev_device_ref(dev->dev);
+ return;
+
+err_dev:
+ hashmap_remove(mon->devices_by_devt, &devt);
+ hashmap_remove(mon->devices, s);
+ free(dev);
+err_out:
+ if (r < 0)
+ log_error("gfx: cannot add device %s: %d", s, r);
+}
+
+static void gfx_device_free(gfx_device *dev) {
+ const char *s;
+
+ s = udev_device_get_syspath(dev->dev);
+ log_debug("gfx: free device %s", s);
+
+ switch (dev->devtype) {
+ case SD_GFX_DEV_CARD:
+ gfx_device_free_card(dev);
+ break;
+ case SD_GFX_DEV_KBD:
+ gfx_device_free_kbd(dev);
+ break;
+ }
+
+ sd_bus_call_async_cancel(dev->mon->bus, dev->logind_open_req);
+ hashmap_remove(dev->mon->devices_by_devt, &dev->devt);
+ hashmap_remove(dev->mon->devices, s);
+ udev_device_unref(dev->dev);
+ free(dev);
+}
+
+static void gfx_device_change(gfx_device *dev, struct udev_device *d) {
+ /* TODO: ... */
+}
+
+static gfx_device *gfx_monitor_find(sd_gfx_monitor *mon, struct udev_device *d) {
+ const char *s;
+
+ s = udev_device_get_syspath(d);
+ if (!s)
+ return NULL;
+
+ return hashmap_get(mon->devices, s);
+}
+
+/*
+ * Scanner
+ */
+
+static void gfx_monitor_scan(sd_gfx_monitor *mon) {
+ struct udev_enumerate *e;
+ struct udev_list_entry *l;
+ struct udev_device *d;
+ const char *v;
+ int r;
+
+ log_debug("gfx: scan system");
+
+ e = udev_enumerate_new(mon->udev);
+ if (!e) {
+ log_oom();
+ return;
+ }
+
+ /* We cannot filter for seat-tags as evdev device-nodes are not tagged
+ * (only the input-device parents are tagged). */
+
+ if (mon->devmask & SD_GFX_DEV_CARD) {
+ r = udev_enumerate_add_match_subsystem(e, "drm");
+ if (r < 0)
+ goto error;
+ }
+
+ if (mon->devmask & SD_GFX_DEV_EVDEV_MASK) {
+ r = udev_enumerate_add_match_subsystem(e, "input");
+ if (r < 0)
+ goto error;
+ }
+
+ r = udev_enumerate_add_match_is_initialized(e);
+ if (r < 0)
+ goto error;
+
+ r = udev_enumerate_scan_devices(e);
+ if (r < 0)
+ goto error;
+
+ l = udev_enumerate_get_list_entry(e);
+ for ( ; l; l = udev_list_entry_get_next(l)) {
+ v = udev_list_entry_get_name(l);
+ d = udev_device_new_from_syspath(mon->udev, v);
+ if (d) {
+ if (!gfx_monitor_find(mon, d))
+ gfx_device_new(mon, d);
+ udev_device_unref(d);
+ } else {
+ log_error("gfx: cannot get device %s", v);
+ }
+ }
+
+ udev_enumerate_unref(e);
+ return;
+
+error:
+ log_error("gfx: cannot enumerate udev devices: %d", r);
+ udev_enumerate_unref(e);
+}
+
+static int gfx_monitor_prepare_fn(sd_event_source *source, void *data) {
+ sd_gfx_monitor *mon = data;
+ sd_gfx_monitor_event ev;
+
+ sd_event_source_set_prepare(source, NULL);
+ gfx_monitor_scan(mon);
+
+ if (mon->event_fn) {
+ memset(&ev, 0, sizeof(ev));
+ ev.type = SD_GFX_MONITOR_RUN;
+ mon->event_fn(mon, mon->fn_data, &ev);
+ }
+
+ return 0;
+}
+
+static void gfx_monitor_io(sd_gfx_monitor *mon) {
+ struct udev_device *d;
+ gfx_device *dev;
+ const char *a;
+
+ d = udev_monitor_receive_device(mon->umon);
+ if (!d)
+ return;
+
+ a = udev_device_get_action(d);
+ if (!a)
+ return;
+
+ dev = gfx_monitor_find(mon, d);
+ if (dev) {
+ if (streq(a, "change"))
+ gfx_device_change(dev, d);
+ else if (streq(a, "remove"))
+ gfx_device_free(dev);
+ } else {
+ if (streq(a, "add"))
+ gfx_device_new(mon, d);
+ }
+}
+
+static int gfx_monitor_io_fn(sd_event_source *source, int fd, uint32_t mask, void *data) {
+ sd_gfx_monitor *mon = data;
+
+ if (mask & (EPOLLHUP | EPOLLERR)) {
+ sd_event_source_set_enabled(source, SD_EVENT_OFF);
+ log_error("gfx: HUP on udev-monitor socket");
+ return 0;
+ }
+
+ if (mask & EPOLLIN)
+ gfx_monitor_io(mon);
+
+ return 0;
+}
+
+static int gfx_monitor_prepare_umon(sd_gfx_monitor *mon) {
+ int r, fd;
+
+ mon->umon = udev_monitor_new_from_netlink(mon->udev, "udev");
+ if (!mon->umon)
+ return log_oom();
+
+ /* We cannot filter for seat-tags as evdev device-nodes are not tagged
+ * (only the input-device parents are tagged). */
+
+ if (mon->devmask & SD_GFX_DEV_CARD) {
+ r = udev_monitor_filter_add_match_subsystem_devtype(mon->umon, "drm", NULL);
+ if (r < 0)
+ goto error;
+ }
+
+ if (mon->devmask & SD_GFX_DEV_EVDEV_MASK) {
+ r = udev_monitor_filter_add_match_subsystem_devtype(mon->umon, "input", NULL);
+ if (r < 0)
+ goto error;
+ }
+
+ r = udev_monitor_enable_receiving(mon->umon);
+ if (r < 0)
+ goto error;
+
+ fd = udev_monitor_get_fd(mon->umon);
+ r = sd_event_add_io(mon->event, fd, EPOLLIN, gfx_monitor_io_fn, mon, &mon->umon_source);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_prepare(mon->umon_source, gfx_monitor_prepare_fn);
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+error:
+ log_error("gfx: cannot prepare udev-monitor: %d", r);
+ return r;
+}
+
+/*
+ * Keymaps
+ */
+
+static void gfx_keymap_update(sd_gfx_monitor *mon) {
+ struct xkb_rule_names rmlvo = { };
+ struct xkb_keymap *keymap;
+ struct xkb_state *state;
+ gfx_device *dev;
+ Iterator i;
+
+ log_debug("gfx: change keymap to (%1s, %s, %s, %s)",
+ mon->xkb_model ? : "", mon->xkb_layout ? : "",
+ mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+ rmlvo.rules = "evdev";
+ rmlvo.model = mon->xkb_model;
+ rmlvo.layout = mon->xkb_layout;
+ rmlvo.variant = mon->xkb_variant;
+ rmlvo.options = mon->xkb_options;
+
+ keymap = xkb_keymap_new_from_names(mon->xkb, &rmlvo, 0);
+ if (!keymap) {
+ log_error("gfx: cannot load keymap (%1s, %s, %s, %s)",
+ mon->xkb_model ? : "", mon->xkb_layout ? : "",
+ mon->xkb_variant ? : "", mon->xkb_options ? : "");
+ return;
+ }
+
+ HASHMAP_FOREACH(dev, mon->devices, i) {
+ if (dev->devtype != SD_GFX_DEV_KBD)
+ continue;
+
+ state = sd_gfx_kbd_get_state(dev->kbd);
+ if (xkb_state_get_keymap(state) != mon->keymap)
+ continue;
+
+ state = xkb_state_new(keymap);
+ if (!state) {
+ log_oom();
+ break;
+ }
+
+ sd_gfx_kbd_set_state(dev->kbd, state);
+ xkb_state_unref(state);
+ }
+
+ xkb_keymap_unref(mon->keymap);
+ mon->keymap = keymap;
+}
+
+static const struct bus_properties_map gfx_keymap_map[] = {
+ { "X11Model", "s", NULL, offsetof(sd_gfx_monitor, xkb_model) },
+ { "X11Layout", "s", NULL, offsetof(sd_gfx_monitor, xkb_layout) },
+ { "X11Variant", "s", NULL, offsetof(sd_gfx_monitor, xkb_variant) },
+ { "X11Options", "s", NULL, offsetof(sd_gfx_monitor, xkb_options) },
+ {}
+};
+
+static int gfx_keymap_get_all_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ int r;
+
+ mon->localed_req = 0;
+
+ r = bus_message_map_all_properties(m, gfx_keymap_map, mon);
+ if (r < 0) {
+ log_warning("gfx: cannot fetch keymap from localed: %d", r);
+ return 0;
+ }
+
+ gfx_keymap_update(mon);
+
+ return 0;
+}
+
+static int gfx_keymap_props_changed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ sd_bus_message *req = NULL;
+ int r;
+
+ sd_bus_call_async_cancel(bus, mon->localed_req);
+ mon->localed_req = 0;
+
+ r = bus_message_map_properties_changed(m, gfx_keymap_map, mon);
+ if (r < 0)
+ goto error;
+
+ if (r > 0) {
+ r = sd_bus_message_new_method_call(bus,
+ "org.freedesktop.locale1",
+ "/org/freedesktop/locale1",
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ &req);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_append(req, "s", "org.freedesktop.locale1");
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_call_async(bus, req, gfx_keymap_get_all_fn,
+ mon, 0, &mon->localed_req);
+ if (r < 0)
+ goto error;
+
+ sd_bus_message_unref(req);
+ } else {
+ gfx_keymap_update(mon);
+ }
+
+ return 0;
+
+error:
+ log_error("gfx: cannot request keymap from localed: %d", r);
+ sd_bus_message_unref(req);
+ return 0;
+}
+
+static int gfx_keymap_load(sd_gfx_monitor *mon) {
+ struct xkb_rule_names rmlvo = { };
+ int r;
+
+ if (mon->bus) {
+ log_debug("gfx: fetching keymap from localed..");
+ r = bus_map_all_properties(mon->bus,
+ "org.freedesktop.locale1",
+ "/org/freedesktop/locale1",
+ gfx_keymap_map,
+ mon);
+ if (r < 0)
+ log_warning("gfx: cannot fetch keymap from localed: %d", r);
+
+ r = sd_bus_add_match(mon->bus,
+ "type='signal',"
+ "sender='org.freedesktop.locale1',"
+ "interface='org.freedesktop.DBus.Properties',"
+ "member='PropertiesChanged',"
+ "path='/org/freedesktop/locale1'",
+ gfx_keymap_props_changed_fn,
+ mon);
+ if (r < 0)
+ log_warning("gfx: cannot watch localed for events: %d", r);
+ }
+
+ log_debug("gfx: default keymap is (%1s, %s, %s, %s)",
+ mon->xkb_model ? : "", mon->xkb_layout ? : "",
+ mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+ rmlvo.rules = "evdev";
+ rmlvo.model = mon->xkb_model;
+ rmlvo.layout = mon->xkb_layout;
+ rmlvo.variant = mon->xkb_variant;
+ rmlvo.options = mon->xkb_options;
+
+ mon->keymap = xkb_keymap_new_from_names(mon->xkb, &rmlvo, 0);
+ if (!mon->keymap) {
+ log_error("gfx: cannot load keymap (%1s, %s, %s, %s), trying system-default",
+ mon->xkb_model ? : "", mon->xkb_layout ? : "",
+ mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+ mon->keymap = xkb_keymap_new_from_names(mon->xkb, NULL, 0);
+ if (!mon->keymap) {
+ log_error("gfx: cannot load default keymap");
+
+ /* TODO: we should add a EN-us built-in keymap to
+ * xkbcommon so this cannot happen in case no
+ * x11-keymaps are installed. */
+
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Monitor
+ */
+
+static void gfx_xkb_log_fn(struct xkb_context *context,
+ enum xkb_log_level level,
+ const char *format,
+ va_list args) {
+ unsigned int l;
+
+ switch (level) {
+ case XKB_LOG_LEVEL_CRITICAL:
+ l = LOG_CRIT;
+ break;
+ case XKB_LOG_LEVEL_ERROR:
+ l = LOG_ERR;
+ break;
+ case XKB_LOG_LEVEL_WARNING:
+ l = LOG_WARNING;
+ break;
+ case XKB_LOG_LEVEL_INFO:
+ l = LOG_INFO;
+ break;
+ case XKB_LOG_LEVEL_DEBUG:
+ default:
+ l = LOG_DEBUG;
+ break;
+ }
+
+ log_metav(l, NULL, 0, NULL, format, args);
+}
+
+int sd_gfx_monitor_new(sd_gfx_monitor **out,
+ unsigned int devmask,
+ unsigned int flags,
+ sd_event *event) {
+ sd_gfx_monitor *mon;
+ int r;
+
+ if (!(flags & SD_GFX_MONITOR_IGNORE_SEATS)) {
+ if (flags & SD_GFX_MONITOR_NO_DBUS)
+ return -EINVAL;
+
+ flags |= SD_GFX_MONITOR_REQUIRE_DBUS;
+ }
+
+ log_debug("gfx: new monitor");
+
+ mon = calloc(1, sizeof(*mon));
+ if (!mon)
+ return log_oom();
+
+ mon->devmask = devmask;
+ mon->flags = flags;
+ mon->event = event;
+
+ if (flags & SD_GFX_MONITOR_IGNORE_SEATS) {
+ /* Used by boot-splashs, debugging-apps or other system-wide
+ * gfx-apps. Ignores any seating-information and just returns
+ * all devices. */
+
+ if (geteuid()) {
+ log_error("gfx: need to be root to run in system mode");
+ r = -EACCES;
+ goto err_mon;
+ }
+
+ mon->sid = NULL;
+ mon->seat = NULL;
+ log_debug("gfx: running in system mode");
+ } else {
+ /* Used by login-screens or normal sessions. Returns only
+ * devices attached to the process' seat. Uses
+ * systemd-logind for unprivileged device-access.
+ * This requires the process to be already running in a
+ * systemd-session. Must be guaranteed by the caller. */
+
+ r = sd_pid_get_session(getpid(), &mon->sid);
+ if (r < 0) {
+ log_error("gfx: not running in a logind-session: %d", r);
+ goto err_mon;
+ }
+
+ r = sd_session_get_seat(mon->sid, &mon->seat);
+ if (r < 0) {
+ log_error("gfx: session '%s' invalid or not assigned to a seat: %d",
+ mon->sid, r);
+ goto err_mon;
+ }
+ mon->seat0 = streq(mon->seat, "seat0");
+
+ r = sd_session_get_vt(mon->sid, &mon->vt);
+ if (r == -ENOENT) {
+ mon->vt = 0;
+ } else if (r < 0) {
+ log_error("gfx: cannot get attached VT of session '%s': %d",
+ mon->sid, mon->vt);
+ goto err_mon;
+ }
+
+ if (mon->vt)
+ log_debug("gfx: running in session mode as: %s@%s on VT%u",
+ mon->sid, mon->seat, mon->vt);
+ else
+ log_debug("gfx: running in session mode as: %s@%s without VT",
+ mon->sid, mon->seat);
+ }
+
+ mon->devices = hashmap_new(string_hash_func, string_compare_func);
+ if (!mon->devices) {
+ r = log_oom();
+ goto err_mon;
+ }
+
+ mon->devices_by_devt = hashmap_new(devt_hash_func, devt_compare_func);
+ if (!mon->devices_by_devt) {
+ r = log_oom();
+ goto err_mon;
+ }
+
+ if (!(flags & SD_GFX_MONITOR_NO_DBUS)) {
+ r = sd_bus_open_system(&mon->bus);
+ if (r < 0) {
+ log_warning("gfx: cannot open system bus");
+ if (flags & SD_GFX_MONITOR_REQUIRE_DBUS)
+ goto err_mon;
+ } else {
+ r = sd_bus_attach_event(mon->bus, event, 0);
+ if (r < 0) {
+ log_error("gfx: cannot attach bus event loop: %d", r);
+ goto err_mon;
+ }
+ }
+ }
+
+ mon->udev = udev_new();
+ if (!mon->udev) {
+ r = log_oom();
+ goto err_mon;
+ }
+
+ r = gfx_monitor_prepare_umon(mon);
+ if (r < 0)
+ goto err_mon;
+
+ if (devmask & SD_GFX_DEV_KBD) {
+ mon->xkb = xkb_context_new(0);
+ if (!mon->xkb) {
+ r = log_oom();
+ goto err_mon;
+ }
+ xkb_context_set_log_fn(mon->xkb, gfx_xkb_log_fn);
+
+ r = gfx_keymap_load(mon);
+ if (r < 0)
+ goto err_mon;
+ }
+
+ if (mon->sid) {
+ r = gfx_logind_prepare(mon);
+ if (r < 0)
+ goto err_mon;
+ } else {
+ mon->active = 1;
+ }
+
+ sd_event_ref(mon->event);
+ *out = mon;
+ return 0;
+
+err_mon:
+ sd_bus_call_async_cancel(mon->bus, mon->logind_active_req);
+
+ sd_bus_call_async_cancel(mon->bus, mon->localed_req);
+ xkb_keymap_unref(mon->keymap);
+ xkb_context_unref(mon->xkb);
+
+ sd_event_source_set_enabled(mon->umon_source, SD_EVENT_OFF);
+ sd_event_source_unref(mon->umon_source);
+ udev_monitor_unref(mon->umon);
+
+ udev_unref(mon->udev);
+
+ sd_bus_detach_event(mon->bus);
+ sd_bus_unref(mon->bus);
+
+ hashmap_free(mon->devices_by_devt);
+ hashmap_free(mon->devices);
+ strv_free(mon->conf_gpus);
+ free(mon->xkb_model);
+ free(mon->xkb_layout);
+ free(mon->xkb_variant);
+ free(mon->xkb_options);
+ free(mon->spath);
+ free(mon->seat);
+ free(mon->sid);
+ free(mon);
+ return r;
+}
+
+void sd_gfx_monitor_free(sd_gfx_monitor *mon) {
+ gfx_device *dev;
+
+ if (!mon)
+ return;
+
+ log_debug("gfx: free monitor");
+
+ while ((dev = hashmap_first(mon->devices)))
+ gfx_device_free(dev);
+
+ sd_bus_call_async_cancel(mon->bus, mon->logind_active_req);
+
+ sd_bus_call_async_cancel(mon->bus, mon->localed_req);
+ xkb_keymap_unref(mon->keymap);
+ xkb_context_unref(mon->xkb);
+
+ sd_event_source_set_enabled(mon->umon_source, SD_EVENT_OFF);
+ sd_event_source_unref(mon->umon_source);
+ udev_monitor_unref(mon->umon);
+
+ udev_unref(mon->udev);
+
+ sd_bus_detach_event(mon->bus);
+ sd_bus_unref(mon->bus);
+
+ sd_event_unref(mon->event);
+
+ hashmap_free(mon->devices_by_devt);
+ hashmap_free(mon->devices);
+ strv_free(mon->conf_gpus);
+ free(mon->xkb_model);
+ free(mon->xkb_layout);
+ free(mon->xkb_variant);
+ free(mon->xkb_options);
+ free(mon->spath);
+ free(mon->seat);
+ free(mon->sid);
+ free(mon);
+}
+
+void sd_gfx_monitor_set_fn_data(sd_gfx_monitor *mon, void *fn_data) {
+ mon->fn_data = fn_data;
+}
+
+void *sd_gfx_monitor_get_fn_data(sd_gfx_monitor *mon) {
+ return mon->fn_data;
+}
+
+void sd_gfx_monitor_set_event_fn(sd_gfx_monitor *mon, sd_gfx_monitor_event_fn event_fn) {
+ mon->event_fn = event_fn;
+}
+
+/*
+ * The "gpus=" option allows to configure which GPUs to use. Following options
+ * are supported:
+ *
+ * <default>: The default value (or if empty/NULL) is to use all GPUs that
+ * are available.
+ * "": Reset to default.
+ * "boot": Use boot GPU.
+ * "aux": Use all auxiliary GPUs.
+ * "card<num>": Use DRM device "card<num>".
+ * "<ID_PATH>": Use DRM device with given $ID_PATH.
+ * "<driver>": Use DRM devices with given driver.
+ *
+ * Options can be combined if separated with a comma, eg., "aux,card1". Spaces
+ * are ignored.
+ */
+void sd_gfx_monitor_set_gpus(sd_gfx_monitor *mon, const char *gpus) {
+ char **src, **dst, *c;
+
+ if (mon->conf_gpus) {
+ strv_free(mon->conf_gpus);
+ mon->conf_gpus = NULL;
+ mon->conf_gpus_boot = 0;
+ mon->conf_gpus_aux = 0;
+ }
+
+ log_debug("gfx: set GPU match to '%s'", gpus ? : "");
+
+ if (!gpus || !*gpus)
+ return;
+
+ mon->conf_gpus = strv_split(gpus, "," WHITESPACE);
+ if (!mon->conf_gpus) {
+ log_oom();
+ return;
+ }
+
+ src = mon->conf_gpus;
+ dst = mon->conf_gpus;
+
+ while (*src) {
+ c = *src++;
+ if (streq(c, "boot")) {
+ mon->conf_gpus_boot = 1;
+ free(c);
+ } else if (streq(c, "aux")) {
+ mon->conf_gpus_aux = 1;
+ free(c);
+ } else {
+ *dst++ = c;
+ }
+ }
+
+ *dst = NULL;
+}
+
+/*
+ * The "keymap=" option allows to configure which GPUs to use. Following
+ * options are supported:
+ *
+ * <default>: The default value (or if empty/NULL) is to let xkbcommon
+ * choose the default (usually "us" layout) and query
+ * systemd-localed if bus-connection is available.
+ * "": Reset to default.
+ * "<layout>[,<model>[,<variant>[,<options>]]]":
+ * Specify xkb options directly.
+ */
+void sd_gfx_monitor_set_keymap(sd_gfx_monitor *mon, const char *keymap) {
+ char **slots[] = { &mon->xkb_layout, &mon->xkb_model, &mon->xkb_variant, &mon->xkb_options, NULL };
+ char **v, *t, *w, *state;
+ size_t l, i;
+
+ log_debug("gfx: set keymap to '%s'", keymap ? : "");
+
+ if (!keymap || !*keymap) {
+ free(mon->xkb_model);
+ free(mon->xkb_layout);
+ free(mon->xkb_variant);
+ free(mon->xkb_options);
+ mon->xkb_model = NULL;
+ mon->xkb_layout = NULL;
+ mon->xkb_variant = NULL;
+ mon->xkb_options = NULL;
+ } else {
+ i = 0;
+ FOREACH_WORD_SEPARATOR(w, l, keymap, "," WHITESPACE, state) {
+ v = slots[i++];
+ if (!v)
+ break;
+
+ t = strndup(w, l);
+ if (!t) {
+ log_oom();
+ return;
+ }
+
+ free(*v);
+ *v = t;
+ }
+
+ gfx_keymap_update(mon);
+ }
+}
+
+/*
+ * As sd-gfx may be used as boot-splash, initrd-screen, emergency-console and
+ * more, there is often no way to configure it from a fallback console. Thus,
+ * we allow some rather basic fallback options on the kernel command-line so
+ * users can boot with a limited feature/device set in case something fails.
+ *
+ * These options are not limited to a single application, but rather all
+ * applications that use sd-gfx (and call *_parse_cmdlint) are affected. This
+ * is done on purpose as it should only be used for emergency situations if the
+ * system doesn't boot.
+ * Applications provide their own configurations that you should use if you
+ * want to tweak them.
+ *
+ * Supported options are:
+ * systemd.gpus: See the *_set_gpus() function.
+ * systemd.keymap: See the *_set_keymap() function.
+ */
+void sd_gfx_monitor_parse_cmdline(sd_gfx_monitor *mon) {
+ _cleanup_free_ char *line = NULL;
+ const char *v;
+ char *w, *state;
+ size_t l;
+ int r;
+
+ r = proc_cmdline(&line);
+ if (r < 0)
+ log_error("gfx: cannot read /proc/cmdline: %d", r);
+ if (r <= 0)
+ return;
+
+ FOREACH_WORD_QUOTED(w, l, line, state) {
+ _cleanup_free_ char *word = NULL;
+
+ word = strndup(w, l);
+ if (!word) {
+ log_oom();
+ return;
+ }
+
+ v = startswith(word, "systemd.gpus=");
+ if (v) {
+ sd_gfx_monitor_set_gpus(mon, v);
+ continue;
+ }
+
+ v = startswith(word, "systemd.keymap=");
+ if (v) {
+ sd_gfx_monitor_set_keymap(mon, v);
+ continue;
+ }
+ }
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index 44237a1..64e07f1 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -31,6 +31,7 @@

_SD_BEGIN_DECLARATIONS;

+struct udev_device;
struct xkb_state;

typedef struct sd_gfx_buffer sd_gfx_buffer;
@@ -47,6 +48,9 @@ typedef struct sd_gfx_card sd_gfx_card;
typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
typedef struct sd_gfx_kbd sd_gfx_kbd;

+typedef struct sd_gfx_monitor_event sd_gfx_monitor_event;
+typedef struct sd_gfx_monitor sd_gfx_monitor;
+
/* memory buffer */

enum {
@@ -339,6 +343,63 @@ void sd_gfx_kbd_wake_up(sd_gfx_kbd *kbd, int fd);
void sd_gfx_kbd_sleep(sd_gfx_kbd *kbd);
bool sd_gfx_kbd_is_awake(sd_gfx_kbd *kbd);

+/* monitor */
+
+enum {
+ SD_GFX_DEV_UNKNOWN = 0,
+ SD_GFX_DEV_CARD = (1 << 0),
+ SD_GFX_DEV_KBD = (1 << 1),
+
+ SD_GFX_DEV_EVDEV_MASK = SD_GFX_DEV_KBD,
+ SD_GFX_DEV_ALL = SD_GFX_DEV_CARD |
+ SD_GFX_DEV_EVDEV_MASK,
+};
+
+enum {
+ SD_GFX_MONITOR_RUN,
+ SD_GFX_MONITOR_CREATE,
+ SD_GFX_MONITOR_DESTROY,
+};
+
+struct sd_gfx_monitor_event {
+ unsigned int type;
+ unsigned int devtype;
+
+ union {
+ sd_gfx_card *card;
+ sd_gfx_kbd *kbd;
+ };
+};
+
+typedef void (*sd_gfx_monitor_event_fn) (sd_gfx_monitor *mon,
+ void *data,
+ sd_gfx_monitor_event *ev);
+
+enum {
+ SD_GFX_MONITOR_DEFAULT = 0,
+ SD_GFX_MONITOR_IGNORE_SEATS = (1 << 0),
+ SD_GFX_MONITOR_NO_DBUS = (1 << 1),
+ SD_GFX_MONITOR_REQUIRE_DBUS = (1 << 2),
+
+ SD_GFX_MONITOR_MASK = SD_GFX_MONITOR_IGNORE_SEATS |
+ SD_GFX_MONITOR_NO_DBUS |
+ SD_GFX_MONITOR_REQUIRE_DBUS,
+};
+
+int sd_gfx_monitor_new(sd_gfx_monitor **out,
+ unsigned int devmask,
+ unsigned int flags,
+ sd_event *event);
+void sd_gfx_monitor_free(sd_gfx_monitor *mon);
+
+void sd_gfx_monitor_set_fn_data(sd_gfx_monitor *mon, void *fn_data);
+void *sd_gfx_monitor_get_fn_data(sd_gfx_monitor *mon);
+void sd_gfx_monitor_set_event_fn(sd_gfx_monitor *mon, sd_gfx_monitor_event_fn event_fn);
+
+void sd_gfx_monitor_set_gpus(sd_gfx_monitor *mon, const char *gpus);
+void sd_gfx_monitor_set_keymap(sd_gfx_monitor *mon, const char *keymap);
+void sd_gfx_monitor_parse_cmdline(sd_gfx_monitor *mon);
+
_SD_END_DECLARATIONS;

#endif
--
1.8.4.2
Lennart Poettering
2013-11-27 22:34:15 UTC
Permalink
Post by David Herrmann
While all previous sd-gfx interfaces are self-contained and can be used
directly on selected devices, this adds an interface to connect them all
together. The sd_gfx_monitor can be used to monitor a session for device
hotplugging and other device events. It is optional but is supposed to be
the foundation of all systemd-helpers that use sd-gfx.
The main function of sd_gfx_monitor is to watch the system for udev-events
of gpus and keyboards. For each of them, an sd_gfx_card or sd_gfx_kbd
device is created and advertised to the application. Furthermore,
systemd-localed integration is provided so keymap changes are immediately
noticed and applied to active sd_gfx_kbd devices.
- system mode: In system mode, no dbus, no logind and localed are assumed
to be running and seat-information is ignored. This mode
allows to run applications in initrds or emergency
situations. It simply takes all devices it can find and
tries to use them. However, this obviously requires to be
root, otherwise, devices cannot be accessed.
- session mode: In session mode, the monitor assumes to be running in an
logind session. If not, it returns an error. The monitor
will call TakeControl on the active session and get
device-access via logind. Only devices attached to the
session will be used and no you're not required to be
root. The caller is responsible of setting up the session
before spawning the monitor.
Note that monitor setup is a blocking call as it is usually called during
application setup (and making that async would have no gain). But at
runtime, the monitor runs all dbus-calls and other calls asynchronously.
The sd_gfx_monitor interface is designed for fallbacks and basic system
applications. It does not allow per-device configurations or any advanced
eye-candy. It is trimmed for usability and simplicity, and it is optimized
for fallback/emergency situations. Thus, the monitor provides some basic
configuration options via the kernel command-line. systemd.gpus= allows to
filter the GPUs to be used (by default, *all* connected GPUs are used
together). systemd.keymap= allows to change the keymap in case localed is
configured incorrectly.
The sd_gfx_monitor interfaces has the nice side-effect that all
applications using it will share the same configuration. They will have
the same monitor-setup, the same keymap setup and use the same devices. So
if you system-boot fails, you can set systemd.XY="" to boot with a working
configuration and all systemd-internal applications will just work.
If we ever export sd-gfx, most users probably want more configurability
(like per-device keymaps) and thus will not use sd_gfx_monitor. However,
for all fallbacks, it is the perfect base.
Hmm, this bit sounds a bit too high-level for including it in a library,
no? I mean, we can certainly share this bit inside of systemd, but this
sounds too specialized to ever become a public lib, no?
Post by David Herrmann
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &paused);
+ if (r < 0)
+ goto error;
Why in two steps? sd_bus_message_read(m, "hb", &fd, &paused) should work too?
Post by David Herrmann
+static int gfx_logind_resume_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r, fd;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
Same here... One call should suffice...
Post by David Herrmann
+static int gfx_logind_pause_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r;
+ const char *type;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &type);
+ if (r < 0)
+ goto error;
and here...
Post by David Herrmann
+ r = asprintf(&v,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Session',"
+ "member='PauseDevice',"
+ "path='%s'",
+ mon->spath);
asprintf() is actually disgustingly slow. Not that it would matter much
int this case here, but strjoin() is much nicer and measurably faster
for simple concatenation.
Post by David Herrmann
+static int gfx_logind_prepare(sd_gfx_monitor *mon) {
+ _cleanup_free_ char *sid = NULL;
+ _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+ int r;
+
+ sid = sd_bus_label_escape(mon->sid);
+ if (!sid)
+ return log_oom();
+
+ r = asprintf(&mon->spath, "/org/freedesktop/login1/session/%s", sid);
+ if (r < 0)
+ goto error;
strappend() as special case of strjoin() for only two strings is even
better. Or, in this case, since you never hand out the string you can
even use strappenda()...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:41:07 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:34 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
While all previous sd-gfx interfaces are self-contained and can be used
directly on selected devices, this adds an interface to connect them all
together. The sd_gfx_monitor can be used to monitor a session for device
hotplugging and other device events. It is optional but is supposed to be
the foundation of all systemd-helpers that use sd-gfx.
The main function of sd_gfx_monitor is to watch the system for udev-events
of gpus and keyboards. For each of them, an sd_gfx_card or sd_gfx_kbd
device is created and advertised to the application. Furthermore,
systemd-localed integration is provided so keymap changes are immediately
noticed and applied to active sd_gfx_kbd devices.
- system mode: In system mode, no dbus, no logind and localed are assumed
to be running and seat-information is ignored. This mode
allows to run applications in initrds or emergency
situations. It simply takes all devices it can find and
tries to use them. However, this obviously requires to be
root, otherwise, devices cannot be accessed.
- session mode: In session mode, the monitor assumes to be running in an
logind session. If not, it returns an error. The monitor
will call TakeControl on the active session and get
device-access via logind. Only devices attached to the
session will be used and no you're not required to be
root. The caller is responsible of setting up the session
before spawning the monitor.
Note that monitor setup is a blocking call as it is usually called during
application setup (and making that async would have no gain). But at
runtime, the monitor runs all dbus-calls and other calls asynchronously.
The sd_gfx_monitor interface is designed for fallbacks and basic system
applications. It does not allow per-device configurations or any advanced
eye-candy. It is trimmed for usability and simplicity, and it is optimized
for fallback/emergency situations. Thus, the monitor provides some basic
configuration options via the kernel command-line. systemd.gpus= allows to
filter the GPUs to be used (by default, *all* connected GPUs are used
together). systemd.keymap= allows to change the keymap in case localed is
configured incorrectly.
The sd_gfx_monitor interfaces has the nice side-effect that all
applications using it will share the same configuration. They will have
the same monitor-setup, the same keymap setup and use the same devices. So
if you system-boot fails, you can set systemd.XY="" to boot with a working
configuration and all systemd-internal applications will just work.
If we ever export sd-gfx, most users probably want more configurability
(like per-device keymaps) and thus will not use sd_gfx_monitor. However,
for all fallbacks, it is the perfect base.
Hmm, this bit sounds a bit too high-level for including it in a library,
no? I mean, we can certainly share this bit inside of systemd, but this
sounds too specialized to ever become a public lib, no?
Yepp. As said in my comment on 0/12, I squashed it all in a single
header for now. If we export it, this will get moved into gfx-util.h
or similar.
Post by Lennart Poettering
Post by David Herrmann
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &paused);
+ if (r < 0)
+ goto error;
Why in two steps? sd_bus_message_read(m, "hb", &fd, &paused) should work too?
Sweet! Fixed.
Post by Lennart Poettering
Post by David Herrmann
+static int gfx_logind_resume_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r, fd;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+ if (r < 0)
+ goto error;
Same here... One call should suffice...
Post by David Herrmann
+static int gfx_logind_pause_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+ sd_gfx_monitor *mon = data;
+ gfx_device *dev;
+ int r;
+ const char *type;
+ uint32_t major, minor;
+ dev_t devt;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+ if (r < 0)
+ goto error;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &type);
+ if (r < 0)
+ goto error;
and here...
Post by David Herrmann
+ r = asprintf(&v,
+ "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.Session',"
+ "member='PauseDevice',"
+ "path='%s'",
+ mon->spath);
asprintf() is actually disgustingly slow. Not that it would matter much
int this case here, but strjoin() is much nicer and measurably faster
for simple concatenation.
Yepp, fixed.
Post by Lennart Poettering
Post by David Herrmann
+static int gfx_logind_prepare(sd_gfx_monitor *mon) {
+ _cleanup_free_ char *sid = NULL;
+ _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+ int r;
+
+ sid = sd_bus_label_escape(mon->sid);
+ if (!sid)
+ return log_oom();
+
+ r = asprintf(&mon->spath, "/org/freedesktop/login1/session/%s", sid);
+ if (r < 0)
+ goto error;
strappend() as special case of strjoin() for only two strings is even
better. Or, in this case, since you never hand out the string you can
even use strappenda()...
Fixed. But strappenda() doesn't work. I save that string in mon->spath
so I can reuse it for all the runtime dbus requests.

Thanks
David
David Herrmann
2013-11-27 18:48:44 UTC
Permalink
The test-kbd helper simply attaches to the current session (or in case
--all is passed to the system) and prints all keyboard events generated by
attached keyboards.

It's meant to be used to debug sd_gfx_kbd/monitor issues regarding
keymap/kbd setup.
---
.gitignore | 1 +
Makefile.am | 16 +++
src/libsystemd-gfx/test-kbd.c | 314 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 331 insertions(+)
create mode 100644 src/libsystemd-gfx/test-kbd.c

diff --git a/.gitignore b/.gitignore
index f8f6c8a..f4921f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -131,6 +131,7 @@
/test-journal-init
/test-journal-syslog
/test-journal-verify
+/test-kbd
/test-libudev
/test-list
/test-log
diff --git a/Makefile.am b/Makefile.am
index 189cc89..e8822b2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3878,6 +3878,22 @@ libsystemd_gfx_la_LIBADD = \
libudev-internal.la \
src/libsystemd-gfx/unifont.bin.lo

+test_kbd_SOURCES = \
+ src/libsystemd-gfx/test-kbd.c
+
+test_kbd_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GFX_CFLAGS)
+
+test_kbd_LDADD = \
+ $(GFX_LIBS) \
+ libsystemd-bus-internal.la \
+ libsystemd-shared.la \
+ libsystemd-gfx.la
+
+tests += \
+ test-kbd
+
src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
$(AM_V_GEN)cat $(top_srcdir)/src/libsystemd-gfx/unifont.hex | $(PYTHON) $< >$@

diff --git a/src/libsystemd-gfx/test-kbd.c b/src/libsystemd-gfx/test-kbd.c
new file mode 100644
index 0000000..b902171
--- /dev/null
+++ b/src/libsystemd-gfx/test-kbd.c
@@ -0,0 +1,314 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libevdev/libevdev.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "build.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+typedef struct Test Test;
+
+struct Test {
+ sd_event *event;
+ sd_event_source *sigs[_NSIG];
+ sd_gfx_monitor *mon;
+};
+
+static bool arg_all;
+static const char *arg_gpus;
+static const char *arg_keymap;
+
+static void test_kbd_fn(sd_gfx_kbd *kbd, void *fn_data, sd_gfx_kbd_event *ev) {
+ unsigned int i;
+ const char *t;
+ char s[128];
+ uint32_t c;
+
+ printf(" %5d", ev->keycode);
+ t = libevdev_event_code_get_name(EV_KEY, ev->keycode);
+ printf(" | %20s", t ? : "<unknown>");
+
+ printf(" | %s", (ev->mods & SD_GFX_SHIFT) ? "SHIFT" : " ");
+ printf(" | %s", (ev->mods & SD_GFX_CAPSL) ? "CAPSL" : " ");
+ printf(" | %s", (ev->mods & SD_GFX_CTRL) ? "CTRL" : " ");
+ printf(" | %s", (ev->mods & SD_GFX_ALT) ? "ALT" : " ");
+ printf(" | %s", (ev->mods & SD_GFX_LOGO) ? "LOGO" : " ");
+
+ printf(" |");
+ for (i = 0; i < ev->sym_count; ++i) {
+ xkb_keysym_get_name(ev->keysyms[i], s, sizeof(s));
+ printf(" %20s", s);
+ }
+
+ printf(" |");
+ for (i = 0; i < ev->sym_count; ++i) {
+ c = ev->codepoints[i];
+ if (c == 0xffffffff)
+ continue;
+ if (c < 0x1f)
+ continue;
+ if (c >= 0x7f && c <= 0x9f)
+ continue;
+
+ /* Just a proof-of-concept hack. This works because glibc uses
+ * UCS-4 as the internal wchar_t encoding. */
+ printf(" %lc", ev->codepoints[i]);
+ }
+
+ printf("\n");
+}
+
+static void test_add(Test *t, sd_gfx_kbd *kbd) {
+ sd_gfx_kbd_set_event_fn(kbd, test_kbd_fn);
+}
+
+static void test_remove(Test *t, sd_gfx_kbd *kbd) {
+ sd_gfx_kbd_set_event_fn(kbd, NULL);
+}
+
+static void test_event_fn(sd_gfx_monitor *mon, void *fn_data, sd_gfx_monitor_event *ev) {
+ struct Test *t = fn_data;
+
+ switch (ev->type) {
+ case SD_GFX_MONITOR_CREATE:
+ if (ev->devtype == SD_GFX_DEV_KBD)
+ test_add(t, ev->kbd);
+ else
+ log_notice("unknown device of type %d", ev->devtype);
+ break;
+ case SD_GFX_MONITOR_DESTROY:
+ if (ev->devtype == SD_GFX_DEV_KBD)
+ test_remove(t, ev->kbd);
+ break;
+ default:
+ log_warning("unhandled monitor event: %d", ev->type);
+ break;
+ }
+}
+
+static int test_signal_fn(sd_event_source *s, const struct signalfd_siginfo *ssi, void *data) {
+ Test *t = data;
+
+ log_notice("catched signal %d, exiting..", (int)ssi->ssi_signo);
+ sd_event_request_quit(t->event);
+
+ return 0;
+}
+
+static int test_new(Test **out) {
+ static const int sigs[] = {
+ SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, 0
+ };
+ unsigned int flags;
+ sigset_t mask;
+ Test *t;
+ int r, i;
+
+ t = calloc(1, sizeof(*t));
+ if (!t)
+ return log_oom();
+
+ r = sd_event_default(&t->event);
+ if (r < 0) {
+ log_error("cannot get default event-loop: %d", r);
+ goto err_t;
+ }
+
+ sigemptyset(&mask);
+ for (i = 0; sigs[i]; ++i) {
+ sigaddset(&mask, sigs[i]);
+ r = sd_event_add_signal(t->event,
+ sigs[i],
+ test_signal_fn,
+ t,
+ &t->sigs[i]);
+ if (r < 0) {
+ log_error("cannot block signal %d: %d",
+ sigs[i], r);
+ goto err_sigs;
+ }
+ }
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+
+ flags = SD_GFX_MONITOR_DEFAULT;
+ if (arg_all)
+ flags |= SD_GFX_MONITOR_IGNORE_SEATS;
+
+ r = sd_gfx_monitor_new(&t->mon,
+ SD_GFX_DEV_EVDEV_MASK,
+ flags,
+ t->event);
+ if (r < 0)
+ goto err_sigs;
+
+ sd_gfx_monitor_set_fn_data(t->mon, t);
+ sd_gfx_monitor_set_event_fn(t->mon, test_event_fn);
+ sd_gfx_monitor_parse_cmdline(t->mon);
+ if (arg_gpus)
+ sd_gfx_monitor_set_gpus(t->mon, arg_gpus);
+ if (arg_keymap)
+ sd_gfx_monitor_set_keymap(t->mon, arg_keymap);
+
+ *out = t;
+ return 0;
+
+err_sigs:
+ for (i = 0; t->sigs[i]; ++i)
+ sd_event_source_unref(t->sigs[i]);
+ sd_event_unref(t->event);
+err_t:
+ free(t);
+ return r;
+}
+
+static void test_free(Test *t) {
+ int i;
+
+ if (!t)
+ return;
+
+ sd_gfx_monitor_free(t->mon);
+ for (i = 0; t->sigs[i]; ++i)
+ sd_event_source_unref(t->sigs[i]);
+ sd_event_unref(t->event);
+ free(t);
+}
+
+static int test_run(Test *t) {
+ return sd_event_loop(t->event);
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] ...\n\n"
+ "sd-gfx keyboard test.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --all Use all devices\n"
+ " --gpus=MATCH List of GPUs to use\n"
+ " --keymap=LMVO XKB keymap to use\n"
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_ALL,
+ ARG_GPUS,
+ ARG_KEYMAP,
+ };
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "all", no_argument, NULL, ARG_ALL },
+ { "gpus", required_argument, NULL, ARG_GPUS },
+ { "keymap", required_argument, NULL, ARG_KEYMAP },
+ {}
+ };
+ int c;
+
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+ switch(c) {
+ case 'h':
+ return help();
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0;
+ case ARG_ALL:
+ arg_all = true;
+ break;
+ case ARG_GPUS:
+ arg_gpus = optarg;
+ break;
+ case ARG_KEYMAP:
+ arg_keymap = optarg;
+ break;
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached("Unhandled option");
+ }
+ }
+
+ if (optind < argc) {
+ log_error("This program does not take arguments.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ Test *t = NULL;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+ setlocale(LC_ALL, "");
+
+ r = parse_argv(argc, argv);
+ if (r < 0)
+ return EXIT_FAILURE;
+ if (r == 0)
+ return EXIT_SUCCESS;
+
+ r = test_new(&t);
+ if (r < 0)
+ goto finish;
+
+ system("stty -echo");
+ r = test_run(t);
+ system("stty echo");
+
+finish:
+ if (t)
+ test_free(t);
+
+ log_debug("exiting..");
+ return abs(r);
+}
--
1.8.4.2
David Herrmann
2013-11-27 18:48:41 UTC
Permalink
To replace the kernel kbd layer, we need keyboard handling in user-space.
The current de-facto standard is XKB and luckily, in the last 3 years
there was an effort to make XKB indepedent of X11, namely libxkbcommon.

libxkbcommon provides keyboard handling compatible (and even superior) to
classic XKB. The default wayland kbd-protocol was designed around the
xkbcommon API and libxkbcommon is used by all known wayland-compositors.
Its only dependency is glibc. However, you also need the keymap-files,
which are part of "xkeyboard-config", which itself has no dependencies and
only provides the uncompiled keymap source files.

The kbd sd-gfx layer takes an evdev fd as input and provides keyboard
presses as events. Everything in between is handled transparently to the
application.

To access the kernel evdev layer, we use libevdev. It is a small wrapper
around the evdev ioctls with no dependencies but libc. The main reason it
was created is to handle SYN_DROPPED (input even-queue overflow)
transparently. It is a mandatory dependency of xf86-input-evdev as of 2013
and will probably be used in most other wayland compositors, too.

The sd_gfx_kbd API should be straightforward. The only thing to mention is
async-sleep support. That is, whenever your session is moved into
background, the evdev fd is revoked. sd_gfx_kbd detects that and puts the
device asleep. Explicit sleep is also supported via sd_gfx_kbd_sleep().
You need to provide a new fd to wake the device up.
---
Makefile.am | 8 +-
configure.ac | 7 +-
src/libsystemd-gfx/gfx-kbd.c | 629 +++++++++++++++++++++++++++++++++++++++++++
src/systemd/sd-gfx.h | 84 ++++++
4 files changed, 725 insertions(+), 3 deletions(-)
create mode 100644 src/libsystemd-gfx/gfx-kbd.c

diff --git a/Makefile.am b/Makefile.am
index 2edb091..6ecbd50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3859,12 +3859,18 @@ noinst_LTLIBRARIES += \

libsystemd_gfx_la_SOURCES = \
src/libsystemd-gfx/sd-gfx.h \
+ src/libsystemd-gfx/gfx-kbd.c \
src/libsystemd-gfx/gfx-unifont.c

libsystemd_gfx_la_CFLAGS = \
- $(AM_CFLAGS)
+ $(AM_CFLAGS) \
+ $(GFX_CFLAGS)

libsystemd_gfx_la_LIBADD = \
+ $(GFX_LIBS) \
+ libsystemd-bus-internal.la \
+ libsystemd-daemon-internal.la \
+ libsystemd-id128-internal.la \
libsystemd-shared.la \
src/libsystemd-gfx/unifont.bin.lo

diff --git a/configure.ac b/configure.ac
index 354673a..74c37ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -294,8 +294,11 @@ AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
have_gfx=no
AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
if test "x$enable_gfx" != "xno"; then
- AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built])
- have_gfx=yes
+ PKG_CHECK_MODULES(GFX, [ libevdev >= 0.4 xkbcommon >= 0.3 ],
+ [AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built]) have_gfx=yes], have_gfx=no)
+ if test "x$have_gfx" = xno -a "x$enable_gfx" = xyes; then
+ AC_MSG_ERROR([*** sd-gfx support requested, but libraries not found])
+ fi
fi
AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])

diff --git a/src/libsystemd-gfx/gfx-kbd.c b/src/libsystemd-gfx/gfx-kbd.c
new file mode 100644
index 0000000..4de21dd
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-kbd.c
@@ -0,0 +1,629 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libevdev/libevdev.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "time-util.h"
+#include "util.h"
+
+enum {
+ SD_GFX__LED_NUML,
+ SD_GFX__LED_CAPSL,
+ SD_GFX__LED_SCROLLL,
+ SD_GFX__LED_COUNT,
+};
+
+struct sd_gfx_kbd {
+ char *name;
+ struct libevdev *evdev;
+ int fd;
+ struct xkb_state *state;
+ sd_event *ev;
+ sd_event_source *fd_source;
+ sd_event_source *timer;
+ sd_gfx_kbd_event_fn event_fn;
+ void *data;
+ void *fn_data;
+
+ unsigned int delay;
+ unsigned int rate;
+
+ unsigned int sym_count;
+ sd_gfx_kbd_event event;
+ sd_gfx_kbd_event repeat;
+
+ xkb_mod_index_t xkb_mods[SD_GFX__MOD_COUNT];
+ xkb_led_index_t xkb_leds[SD_GFX__LED_COUNT];
+
+ unsigned int awake : 1;
+ unsigned int need_sync : 1;
+ unsigned int repeating : 1;
+};
+
+/* xkb rules="evdev" shifts linux keycodes by +8 */
+#define GFX_XKBCODE(_code) ((_code) + 8)
+
+static void gfx_kbd_update_leds(sd_gfx_kbd *kbd) {
+ enum libevdev_led_value leds[SD_GFX__LED_COUNT];
+ unsigned int i;
+ int r;
+
+ for (i = 0; i < SD_GFX__LED_COUNT; ++i) {
+ if (kbd->xkb_leds[i] != XKB_LED_INVALID &&
+ xkb_state_led_index_is_active(kbd->state, kbd->xkb_leds[i]) > 0)
+ leds[i] = LIBEVDEV_LED_ON;
+ else
+ leds[i] = LIBEVDEV_LED_OFF;
+ }
+
+ r = libevdev_kernel_set_led_values(kbd->evdev,
+ LED_NUML, leds[SD_GFX__LED_NUML],
+ LED_CAPSL, leds[SD_GFX__LED_CAPSL],
+ LED_SCROLLL, leds[SD_GFX__LED_SCROLLL],
+ -1);
+ if (r < 0) {
+ if (r == -EACCES)
+ log_debug("kbd %s: EACCES on led-update", kbd->name);
+ else
+ log_error("kbd %s: cannot update leds: %d",
+ kbd->name, r);
+ }
+}
+
+static int gfx_kbd_resize(sd_gfx_kbd *kbd, size_t s)
+{
+ uint32_t *tmp;
+
+ if (s > kbd->sym_count) {
+ tmp = realloc(kbd->event.keysyms, sizeof(uint32_t) * s);
+ if (!tmp)
+ return log_oom();
+ kbd->event.keysyms = tmp;
+
+ tmp = realloc(kbd->event.codepoints, sizeof(uint32_t) * s);
+ if (!tmp)
+ return log_oom();
+ kbd->event.codepoints = tmp;
+
+ tmp = realloc(kbd->repeat.keysyms, sizeof(uint32_t) * s);
+ if (!tmp)
+ return log_oom();
+ kbd->repeat.keysyms = tmp;
+
+ tmp = realloc(kbd->repeat.codepoints, sizeof(uint32_t) * s);
+ if (!tmp)
+ return log_oom();
+ kbd->repeat.codepoints = tmp;
+
+ kbd->sym_count = s;
+ }
+
+ return 0;
+}
+
+/*
+ * If multiple layouts are used in a single keymap, one might be US-compatible,
+ * the other might not. If the latter is active, a shortcut like ctrl+c in
+ * terminals/etc. is still expected to work. This helper tries to find a
+ * US-compatible/ASCII-compatible key in all layouts for a given keycode.
+ * The strategy is:
+ * - Try the current mapping. If it's ascii-compat, we're done.
+ * - Go through all layouts for the key, starting at 0:
+ * - Map the key in the currently active level, if it's ascii, we're done.
+ * - Return XKB_KEY_NoSymbol
+ */
+static uint32_t gfx_get_ascii(struct xkb_state *state,
+ uint32_t keycode,
+ unsigned int num_keysyms,
+ const uint32_t *keysyms) {
+ struct xkb_keymap *keymap;
+ xkb_layout_index_t num_layouts;
+ xkb_layout_index_t layout;
+ xkb_level_index_t level;
+ const xkb_keysym_t *syms;
+ int num_syms;
+
+ if (num_keysyms == 1 && keysyms[0] < 128)
+ return keysyms[0];
+
+ keymap = xkb_state_get_keymap(state);
+ num_layouts = xkb_keymap_num_layouts_for_key(keymap, GFX_XKBCODE(keycode));
+
+ for (layout = 0; layout < num_layouts; layout++) {
+ level = xkb_state_key_get_level(state, GFX_XKBCODE(keycode), layout);
+ num_syms = xkb_keymap_key_get_syms_by_level(keymap, GFX_XKBCODE(keycode), layout, level, &syms);
+ if (num_syms != 1)
+ continue;
+
+ if (syms[0] < 128)
+ return syms[0];
+ }
+
+ return XKB_KEY_NoSymbol;
+}
+
+static int gfx_kbd_fill(sd_gfx_kbd *kbd, sd_gfx_kbd_event *ev, xkb_keycode_t code, unsigned int num, const xkb_keysym_t *syms) {
+ int r;
+ unsigned int i;
+
+ r = gfx_kbd_resize(kbd, num);
+ if (r < 0)
+ return r;
+
+ ev->mods = 0;
+
+ for (i = 0; i < SD_GFX__MOD_COUNT; ++i) {
+ if (kbd->xkb_mods[i] != XKB_MOD_INVALID) {
+ r = xkb_state_mod_index_is_active(kbd->state,
+ kbd->xkb_mods[i],
+ XKB_STATE_MODS_EFFECTIVE);
+ if (r > 0)
+ ev->mods |= (1 << i);
+
+ r = xkb_state_mod_index_is_consumed(kbd->state,
+ GFX_XKBCODE(code),
+ kbd->xkb_mods[i]);
+ if (r > 0)
+ ev->consumed_mods |= (1 << i);
+ }
+ }
+
+ ev->keycode = code;
+ ev->ascii = gfx_get_ascii(kbd->state, code, num, syms);
+
+ ev->sym_count = num;
+ memcpy(ev->keysyms, syms, sizeof(uint32_t) * num);
+
+ for (i = 0; i < num; ++i) {
+ ev->codepoints[i] = xkb_keysym_to_utf32(syms[i]);
+ if (!ev->codepoints[i])
+ ev->codepoints[i] = 0xffffffffULL;
+ }
+
+ return 0;
+}
+
+static int gfx_kbd_timer_fn(sd_event_source *source, uint64_t usec, void *data);
+
+static void gfx_kbd_arm(sd_gfx_kbd *kbd, uint64_t usec) {
+ int r = 0;
+
+ if (usec) {
+ usec += now(CLOCK_MONOTONIC);
+
+ if (!kbd->timer) {
+ r = sd_event_add_monotonic(kbd->ev,
+ usec,
+ 10 * USEC_PER_MSEC,
+ gfx_kbd_timer_fn,
+ kbd,
+ &kbd->timer);
+ } else {
+ r = sd_event_source_set_time(kbd->timer, usec);
+ if (r >= 0)
+ r = sd_event_source_set_enabled(kbd->timer, SD_EVENT_ON);
+ }
+ } else if (kbd->timer) {
+ r = sd_event_source_set_enabled(kbd->timer, SD_EVENT_OFF);
+ }
+
+ if (r < 0 && r != -ESTALE)
+ log_error("kbd %s: cannot arm/disarm repeat timer: %d",
+ kbd->name, r);
+}
+
+static int gfx_kbd_timer_fn(sd_event_source *source, uint64_t usec, void *data) {
+ sd_gfx_kbd *kbd = data;
+
+ gfx_kbd_arm(kbd, kbd->rate * 1000ULL);
+ if (kbd->event_fn)
+ kbd->event_fn(kbd, kbd->fn_data, &kbd->repeat);
+
+ return 0;
+}
+
+static void gfx_kbd_repeat(sd_gfx_kbd *kbd, bool state) {
+ struct xkb_keymap *keymap = xkb_state_get_keymap(kbd->state);
+ const uint32_t *keysyms;
+ unsigned int i;
+ int num;
+
+ if (kbd->repeating && kbd->repeat.keycode == kbd->event.keycode) {
+ if (!state) {
+ kbd->repeating = 0;
+ gfx_kbd_arm(kbd, 0);
+ }
+
+ return;
+ }
+
+ if (state && xkb_keymap_key_repeats(keymap, GFX_XKBCODE(kbd->event.keycode))) {
+ kbd->repeat.mods = kbd->event.mods;
+ kbd->repeat.consumed_mods = kbd->event.consumed_mods;
+ kbd->repeat.keycode = kbd->event.keycode;
+ kbd->repeat.ascii = kbd->event.ascii;
+ kbd->repeat.sym_count = kbd->event.sym_count;
+
+ for (i = 0; i < kbd->event.sym_count; ++i) {
+ kbd->repeat.keysyms[i] = kbd->event.keysyms[i];
+ kbd->repeat.codepoints[i] = kbd->event.codepoints[i];
+ }
+
+ kbd->repeating = 1;
+ gfx_kbd_arm(kbd, kbd->delay * 1000ULL);
+ } else if (kbd->repeating && !xkb_keymap_key_repeats(keymap, GFX_XKBCODE(kbd->event.keycode))) {
+ num = xkb_state_key_get_syms(kbd->state, GFX_XKBCODE(kbd->repeat.keycode), &keysyms);
+ if (num <= 0)
+ return;
+
+ gfx_kbd_fill(kbd, &kbd->repeat, kbd->repeat.keycode, num, keysyms);
+ return;
+ }
+}
+
+static void gfx_kbd_event(sd_gfx_kbd *kbd, struct input_event *ev, bool in_sync) {
+ const xkb_keysym_t *keysyms;
+ int num, r;
+ enum xkb_state_component ch;
+
+ if (ev->type != EV_KEY || ev->value < 0 || ev->value > 1)
+ return;
+
+ /* TODO: If @in_sync XKB shouldn't trigger xkb-actions. However, there
+ * is no flag for that, yet. */
+
+ num = xkb_state_key_get_syms(kbd->state, GFX_XKBCODE(ev->code), &keysyms);
+ ch = xkb_state_update_key(kbd->state, GFX_XKBCODE(ev->code), ev->value);
+
+ if (ch & XKB_STATE_LEDS)
+ gfx_kbd_update_leds(kbd);
+
+ if (num > 0) {
+ r = gfx_kbd_fill(kbd, &kbd->event, ev->code, num, keysyms);
+ if (r < 0)
+ return;
+
+ /* Synced events are reported out of order. We must not use
+ * them as application input but only to sync our modifier
+ * state. */
+ if (in_sync)
+ return;
+
+ gfx_kbd_repeat(kbd, ev->value);
+
+ if (ev->value && kbd->event_fn)
+ kbd->event_fn(kbd, kbd->fn_data, &kbd->event);
+ }
+}
+
+static void gfx_kbd_io(sd_gfx_kbd *kbd) {
+ int r;
+ struct input_event ev;
+ unsigned int flags = LIBEVDEV_READ_FLAG_NORMAL;
+
+ if (!kbd->awake)
+ return;
+
+ if (kbd->need_sync) {
+ flags |= LIBEVDEV_READ_FLAG_FORCE_SYNC;
+
+ r = libevdev_next_event(kbd->evdev, flags, &ev);
+ if (r != LIBEVDEV_READ_STATUS_SYNC)
+ log_error("kbd %s: cannot force resync: %d",
+ kbd->name, r);
+
+ gfx_kbd_update_leds(kbd);
+ gfx_kbd_arm(kbd, 0);
+ kbd->need_sync = 0;
+ flags = LIBEVDEV_READ_FLAG_SYNC;
+ }
+
+ for (;;) {
+ r = libevdev_next_event(kbd->evdev, flags, &ev);
+ if (r == -EAGAIN) {
+ break;
+ } else if (r == LIBEVDEV_READ_STATUS_SUCCESS) {
+ /* Ummm.. libevdev reads events into its own queue on
+ * *each* invocation. Thus, we must read until the
+ * queue is empty, otherwise, we'd have to schedule an
+ * idle-source just for the next event..
+ * So continue reading until we get EAGAIN. */
+ gfx_kbd_event(kbd, &ev, false);
+ } else if (r == LIBEVDEV_READ_STATUS_SYNC) {
+ /* If STATUS_SYNC is returned during normal invocation,
+ * it means we read a SYN_DROPPED event and should sync
+ * the device. We then sync until we get EAGAIN. */
+ if (flags & LIBEVDEV_READ_FLAG_SYNC) {
+ gfx_kbd_event(kbd, &ev, true);
+ } else {
+ flags = LIBEVDEV_READ_FLAG_SYNC;
+ gfx_kbd_arm(kbd, 0);
+ }
+ } else {
+ if (r == -EACCES)
+ log_debug("kbd %s: async sleep", kbd->name);
+ else
+ log_error("kbd %s: read failed: %d",
+ kbd->name, r);
+
+ sd_gfx_kbd_sleep(kbd);
+ break;
+ }
+ }
+}
+
+static int gfx_kbd_io_fn(sd_event_source *source, int fd, uint32_t mask, void *data) {
+ sd_gfx_kbd *kbd = data;
+
+ if (mask & (EPOLLHUP | EPOLLERR)) {
+ log_debug("kbd %s: HUP", kbd->name);
+ sd_event_source_set_enabled(source, SD_EVENT_OFF);
+ return 0;
+ }
+
+ if (mask & EPOLLIN)
+ gfx_kbd_io(kbd);
+
+ return 0;
+}
+
+static int gfx_kbd_prepare_fn(sd_event_source *source, void *data) {
+ sd_gfx_kbd *kbd = data;
+
+ sd_event_source_set_prepare(source, NULL);
+ if (kbd->need_sync)
+ gfx_kbd_io(kbd);
+
+ return 0;
+}
+
+static const char *gfx_mods[] = {
+ [SD_GFX__MOD_SHIFT] = XKB_MOD_NAME_SHIFT,
+ [SD_GFX__MOD_CAPSL] = XKB_MOD_NAME_CAPS,
+ [SD_GFX__MOD_CTRL] = XKB_MOD_NAME_CTRL,
+ [SD_GFX__MOD_ALT] = XKB_MOD_NAME_ALT,
+ [SD_GFX__MOD_LOGO] = XKB_MOD_NAME_LOGO,
+ [SD_GFX__MOD_COUNT] = NULL,
+};
+
+static const char *gfx_leds[] = {
+ [SD_GFX__LED_NUML] = XKB_LED_NAME_NUM,
+ [SD_GFX__LED_CAPSL] = XKB_LED_NAME_CAPS,
+ [SD_GFX__LED_SCROLLL] = XKB_LED_NAME_SCROLL,
+ [SD_GFX__LED_COUNT] = NULL,
+};
+
+static void gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state) {
+ struct xkb_keymap *keymap;
+ unsigned int i;
+ const char *t;
+
+ if (kbd->state) {
+ xkb_state_unref(kbd->state);
+ kbd->state = NULL;
+ kbd->event.state = NULL;
+ kbd->repeat.state = NULL;
+ }
+
+ if (!state)
+ return;
+
+ kbd->state = state;
+ kbd->event.state = state;
+ kbd->repeat.state = state;
+ xkb_state_ref(kbd->state);
+
+ keymap = xkb_state_get_keymap(state);
+
+ for (i = 0; i < SD_GFX__MOD_COUNT; ++i) {
+ t = gfx_mods[i];
+ if (t)
+ kbd->xkb_mods[i] = xkb_keymap_mod_get_index(keymap, t);
+ else
+ kbd->xkb_mods[i] = XKB_MOD_INVALID;
+ }
+
+ for (i = 0; i < SD_GFX__LED_COUNT; ++i) {
+ t = gfx_leds[i];
+ if (t)
+ kbd->xkb_leds[i] = xkb_keymap_led_get_index(keymap, t);
+ else
+ kbd->xkb_leds[i] = XKB_LED_INVALID;
+ }
+
+ /* TODO: We should actually flush the libevdev state into xkb here,
+ * but there's no libevdev call for that. This causes modifier-state
+ * during state-switch to be lost. Seems acceptable.. */
+}
+
+int sd_gfx_kbd_new(sd_gfx_kbd **out,
+ const char *name,
+ struct xkb_state *state,
+ sd_event *event) {
+ sd_gfx_kbd *kbd;
+ int r;
+
+ assert(out);
+ assert(name);
+ assert(state);
+ assert(event);
+
+ log_debug("kbd %s: new", name);
+
+ kbd = calloc(1, sizeof(*kbd));
+ if (!kbd)
+ return log_oom();
+
+ kbd->fd = -1;
+ kbd->ev = event;
+ sd_event_ref(kbd->ev);
+ kbd->delay = 250;
+ kbd->rate = 50;
+ kbd->awake = 0;
+ gfx_kbd_set_state(kbd, state);
+
+ kbd->name = strdup(name);
+ if (!kbd->name) {
+ r = log_oom();
+ goto err_kbd;
+ }
+
+ *out = kbd;
+ return 0;
+
+err_kbd:
+ gfx_kbd_set_state(kbd, NULL);
+ sd_event_unref(kbd->ev);
+ free(kbd);
+ return r;
+}
+
+void sd_gfx_kbd_free(sd_gfx_kbd *kbd) {
+ if (!kbd)
+ return;
+
+ log_debug("kbd %s: free", kbd->name);
+
+ sd_gfx_kbd_sleep(kbd);
+ libevdev_free(kbd->evdev);
+
+ gfx_kbd_set_state(kbd, NULL);
+ sd_event_source_unref(kbd->timer);
+ sd_event_unref(kbd->ev);
+
+ free(kbd->event.keysyms);
+ free(kbd->event.codepoints);
+ free(kbd->repeat.keysyms);
+ free(kbd->repeat.codepoints);
+ free(kbd->name);
+ free(kbd);
+}
+
+void sd_gfx_kbd_set_data(sd_gfx_kbd *kbd, void *data) {
+ kbd->data = data;
+}
+
+void *sd_gfx_kbd_get_data(sd_gfx_kbd *kbd) {
+ return kbd->data;
+}
+
+void sd_gfx_kbd_set_fn_data(sd_gfx_kbd *kbd, void *fn_data) {
+ kbd->fn_data = fn_data;
+}
+
+void *sd_gfx_kbd_get_fn_data(sd_gfx_kbd *kbd) {
+ return kbd->fn_data;
+}
+
+void sd_gfx_kbd_set_event_fn(sd_gfx_kbd *kbd, sd_gfx_kbd_event_fn event_fn) {
+ kbd->event_fn = event_fn;
+}
+
+void sd_gfx_kbd_set_rate(sd_gfx_kbd *kbd, unsigned int delay, unsigned int rate) {
+ kbd->delay = delay ? CLAMP(delay, 10U, 2000U) : 0U;
+ kbd->rate = rate ? CLAMP(rate, 5U, 2000U) : 0U;
+}
+
+void sd_gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state) {
+ if (!state || kbd->state == state)
+ return;
+
+ gfx_kbd_set_state(kbd, state);
+}
+
+struct xkb_state *sd_gfx_kbd_get_state(sd_gfx_kbd *kbd) {
+ return kbd->state;
+}
+
+void sd_gfx_kbd_wake_up(sd_gfx_kbd *kbd, int fd) {
+ int r;
+
+ assert(fd >= 0);
+
+ sd_gfx_kbd_sleep(kbd);
+ log_debug("kbd %s: wake up", kbd->name);
+
+ if (!kbd->evdev) {
+ r = libevdev_new_from_fd(fd, &kbd->evdev);
+ if (r < 0) {
+ log_error("kbd %s: cannot open evdev fd during wake-up: %d",
+ kbd->name, r);
+ goto error;
+ }
+ }
+
+ r = sd_event_add_io(kbd->ev, fd, EPOLLIN, gfx_kbd_io_fn, kbd, &kbd->fd_source);
+ if (r < 0) {
+ log_error("kbd %s: cannot change fd during wake-up: %d",
+ kbd->name, r);
+ goto error;
+ }
+
+ r = sd_event_source_set_prepare(kbd->fd_source, gfx_kbd_prepare_fn);
+ if (r < 0)
+ log_error("kbd %s: cannot schedule kbd-sync on wake-up: %d",
+ kbd->name, r);
+
+ kbd->fd = fd;
+ libevdev_change_fd(kbd->evdev, fd);
+ kbd->need_sync = 1;
+ kbd->awake = 1;
+ return;
+
+error:
+ close_nointr(fd);
+}
+
+void sd_gfx_kbd_sleep(sd_gfx_kbd *kbd) {
+ if (!kbd->awake)
+ return;
+
+ log_debug("kbd %s: sleep", kbd->name);
+ gfx_kbd_arm(kbd, 0);
+
+ sd_event_source_set_enabled(kbd->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(kbd->fd_source);
+ kbd->fd_source = NULL;
+
+ /* TODO: can fail with ENODEV on HUP, ugh? */
+ close_nointr(kbd->fd);
+ kbd->fd = -1;
+ kbd->awake = 0;
+}
+
+bool sd_gfx_kbd_is_awake(sd_gfx_kbd *kbd) {
+ return kbd->awake;
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index c46fbd4..96d034a 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -27,12 +27,19 @@
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/_sd-common.h>
+#include <systemd/sd-event.h>

_SD_BEGIN_DECLARATIONS;

+struct xkb_state;
+
typedef struct sd_gfx_buffer sd_gfx_buffer;
+
typedef struct sd_gfx_font sd_gfx_font;

+typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
+typedef struct sd_gfx_kbd sd_gfx_kbd;
+
/* memory buffer */

enum {
@@ -60,6 +67,83 @@ unsigned int sd_gfx_font_get_height(sd_gfx_font *font);

void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);

+/* keyboard */
+
+enum {
+ SD_GFX_KBD_HUP,
+ SD_GFX_KBD_KEY,
+};
+
+enum {
+ SD_GFX__MOD_SHIFT,
+ SD_GFX__MOD_CAPSL,
+ SD_GFX__MOD_CTRL,
+ SD_GFX__MOD_ALT,
+ SD_GFX__MOD_LOGO,
+ SD_GFX__MOD_COUNT,
+
+ SD_GFX_SHIFT = (1 << SD_GFX__MOD_SHIFT),
+ SD_GFX_CAPSL = (1 << SD_GFX__MOD_CAPSL),
+ SD_GFX_CTRL = (1 << SD_GFX__MOD_CTRL),
+ SD_GFX_ALT = (1 << SD_GFX__MOD_ALT),
+ SD_GFX_LOGO = (1 << SD_GFX__MOD_LOGO),
+};
+
+struct sd_gfx_kbd_event {
+ struct xkb_state *state;
+ uint32_t mods;
+ uint32_t consumed_mods;
+
+ uint16_t keycode;
+ uint32_t ascii;
+
+ unsigned int sym_count;
+ uint32_t *keysyms;
+ uint32_t *codepoints;
+};
+
+static inline bool sd_gfx_kbd_match_shortcut(sd_gfx_kbd_event *event,
+ uint32_t mods,
+ unsigned int sym_count,
+ uint32_t *keysyms) {
+ const uint32_t significant = SD_GFX_SHIFT |
+ SD_GFX_CTRL |
+ SD_GFX_ALT |
+ SD_GFX_LOGO;
+ uint32_t real_mods;
+
+ if (sym_count != event->sym_count)
+ return false;
+
+ real_mods = event->mods & ~event->consumed_mods & significant;
+ if (mods != real_mods)
+ return false;
+
+ return !memcmp(keysyms, event->keysyms, sizeof(uint32_t) * sym_count);
+}
+
+typedef void (*sd_gfx_kbd_event_fn) (sd_gfx_kbd *kbd, void *fn_data, sd_gfx_kbd_event *ev);
+
+int sd_gfx_kbd_new(sd_gfx_kbd **out,
+ const char *name,
+ struct xkb_state *state,
+ sd_event *event);
+void sd_gfx_kbd_free(sd_gfx_kbd *kbd);
+
+void sd_gfx_kbd_set_data(sd_gfx_kbd *kbd, void *data);
+void *sd_gfx_kbd_get_data(sd_gfx_kbd *kbd);
+void sd_gfx_kbd_set_fn_data(sd_gfx_kbd *kbd, void *fn_data);
+void *sd_gfx_kbd_get_fn_data(sd_gfx_kbd *kbd);
+void sd_gfx_kbd_set_event_fn(sd_gfx_kbd *kbd, sd_gfx_kbd_event_fn event_fn);
+
+void sd_gfx_kbd_set_rate(sd_gfx_kbd *kbd, unsigned int delay, unsigned int rate);
+void sd_gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state);
+struct xkb_state *sd_gfx_kbd_get_state(sd_gfx_kbd *kbd);
+
+void sd_gfx_kbd_wake_up(sd_gfx_kbd *kbd, int fd);
+void sd_gfx_kbd_sleep(sd_gfx_kbd *kbd);
+bool sd_gfx_kbd_is_awake(sd_gfx_kbd *kbd);
+
_SD_END_DECLARATIONS;

#endif
--
1.8.4.2
Lennart Poettering
2013-11-27 22:17:20 UTC
Permalink
Post by David Herrmann
+
+enum {
+ SD_GFX__LED_NUML,
+ SD_GFX__LED_CAPSL,
+ SD_GFX__LED_SCROLLL,
+ SD_GFX__LED_COUNT,
+};
Double underscores?
Post by David Herrmann
+
+struct sd_gfx_kbd {
+ char *name;
+ struct libevdev *evdev;
+ int fd;
+ struct xkb_state *state;
+ sd_event *ev;
+ sd_event_source *fd_source;
+ sd_event_source *timer;
+ sd_gfx_kbd_event_fn event_fn;
+ void *data;
+ void *fn_data;
+
+ unsigned int delay;
+ unsigned int rate;
+
+ unsigned int sym_count;
+ sd_gfx_kbd_event event;
+ sd_gfx_kbd_event repeat;
+
+ xkb_mod_index_t xkb_mods[SD_GFX__MOD_COUNT];
+ xkb_led_index_t xkb_leds[SD_GFX__LED_COUNT];
+
+ unsigned int awake : 1;
+ unsigned int need_sync : 1;
+ unsigned int repeating : 1;
If these are bools, then make them bools! C99 bools (i.e. the type
"bool" from stdbool.h) are awesome for bit fields! [ Please use C99 bools
everywhere in internal code, and "int" as bool type for public APIs,
since that's what pre-C99 code usually did, despite the stupidity this
results in when people use bit fields ]
Post by David Herrmann
+ if (r < 0) {
+ if (r == -EACCES)
+ log_debug("kbd %s: EACCES on led-update", kbd->name);
+ else
+ log_error("kbd %s: cannot update leds: %d",
+ kbd->name, r);
Please do not log from libraries. (Well, except when that's the primary
purpose of your function, or when you do so on LOG_DEBUG level, since
that is not visible normally anyway.)

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:36:33 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:17 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+
+enum {
+ SD_GFX__LED_NUML,
+ SD_GFX__LED_CAPSL,
+ SD_GFX__LED_SCROLLL,
+ SD_GFX__LED_COUNT,
+};
Double underscores?
Yes.
Post by Lennart Poettering
Post by David Herrmann
+
+struct sd_gfx_kbd {
+ char *name;
+ struct libevdev *evdev;
+ int fd;
+ struct xkb_state *state;
+ sd_event *ev;
+ sd_event_source *fd_source;
+ sd_event_source *timer;
+ sd_gfx_kbd_event_fn event_fn;
+ void *data;
+ void *fn_data;
+
+ unsigned int delay;
+ unsigned int rate;
+
+ unsigned int sym_count;
+ sd_gfx_kbd_event event;
+ sd_gfx_kbd_event repeat;
+
+ xkb_mod_index_t xkb_mods[SD_GFX__MOD_COUNT];
+ xkb_led_index_t xkb_leds[SD_GFX__LED_COUNT];
+
+ unsigned int awake : 1;
+ unsigned int need_sync : 1;
+ unsigned int repeating : 1;
If these are bools, then make them bools! C99 bools (i.e. the type
"bool" from stdbool.h) are awesome for bit fields! [ Please use C99 bools
everywhere in internal code, and "int" as bool type for public APIs,
since that's what pre-C99 code usually did, despite the stupidity this
results in when people use bit fields ]
You can use "_Bool/bool" for bitfields? Didn't know that. I always use
"bool" for boolean values, I only thought they don't work for
bitfields. Will fix that up.

Thanks
David
Post by Lennart Poettering
Post by David Herrmann
+ if (r < 0) {
+ if (r == -EACCES)
+ log_debug("kbd %s: EACCES on led-update", kbd->name);
+ else
+ log_error("kbd %s: cannot update leds: %d",
+ kbd->name, r);
Please do not log from libraries. (Well, except when that's the primary
purpose of your function, or when you do so on LOG_DEBUG level, since
that is not visible normally anyway.)
Lennart
--
Lennart Poettering, Red Hat
Lennart Poettering
2013-12-11 00:45:11 UTC
Permalink
Post by David Herrmann
Hi
On Wed, Nov 27, 2013 at 11:17 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+
+enum {
+ SD_GFX__LED_NUML,
+ SD_GFX__LED_CAPSL,
+ SD_GFX__LED_SCROLLL,
+ SD_GFX__LED_COUNT,
+};
Double underscores?
Yes.
Why?
Post by David Herrmann
Post by Lennart Poettering
If these are bools, then make them bools! C99 bools (i.e. the type
"bool" from stdbool.h) are awesome for bit fields! [ Please use C99 bools
everywhere in internal code, and "int" as bool type for public APIs,
since that's what pre-C99 code usually did, despite the stupidity this
results in when people use bit fields ]
You can use "_Bool/bool" for bitfields?
Yes, that's what makes them so useful! Otherwise C99 bools are just
meh. But in bitfields, yuppideefuckindoo!
Post by David Herrmann
Didn't know that. I always use "bool" for boolean values, I only
thought they don't work for bitfields. Will fix that up.
Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-27 18:48:45 UTC
Permalink
The test-gfx helper simply renders a solid-color on all active pipelines.
Useful to debug monitor-hotplugging and GPU selection.
---
.gitignore | 1 +
Makefile.am | 9 ++
src/libsystemd-gfx/test-gfx.c | 302 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 312 insertions(+)
create mode 100644 src/libsystemd-gfx/test-gfx.c

diff --git a/.gitignore b/.gitignore
index f4921f5..a61f68d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,6 +115,7 @@
/test-env-replace
/test-event
/test-fileio
+/test-gfx
/test-hashmap
/test-hostname
/test-id128
diff --git a/Makefile.am b/Makefile.am
index e8822b2..aa17876 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3878,6 +3878,14 @@ libsystemd_gfx_la_LIBADD = \
libudev-internal.la \
src/libsystemd-gfx/unifont.bin.lo

+test_gfx_SOURCES = \
+ src/libsystemd-gfx/test-gfx.c
+
+test_gfx_LDADD = \
+ libsystemd-bus-internal.la \
+ libsystemd-shared.la \
+ libsystemd-gfx.la
+
test_kbd_SOURCES = \
src/libsystemd-gfx/test-kbd.c

@@ -3892,6 +3900,7 @@ test_kbd_LDADD = \
libsystemd-gfx.la

tests += \
+ test-gfx \
test-kbd

src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
diff --git a/src/libsystemd-gfx/test-gfx.c b/src/libsystemd-gfx/test-gfx.c
new file mode 100644
index 0000000..36157b6
--- /dev/null
+++ b/src/libsystemd-gfx/test-gfx.c
@@ -0,0 +1,302 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+typedef struct Test Test;
+
+struct Test {
+ sd_event *event;
+ sd_event_source *sigs[_NSIG];
+ sd_gfx_monitor *mon;
+};
+
+static bool arg_all;
+static const char *arg_gpus;
+static const char *arg_keymap;
+
+static void test_pipe_draw(sd_gfx_pipe *pipe) {
+ sd_gfx_plane *primary;
+ sd_gfx_fb *fb;
+
+ primary = sd_gfx_pipe_get_primary_plane(pipe);
+ if (!primary) {
+ log_error("no primary plane on pipe");
+ return;
+ }
+
+ fb = sd_gfx_plane_get_front(primary);
+ if (!fb) {
+ log_error("no front-fb on primary plane");
+ return;
+ }
+
+ sd_gfx_fb_fill(fb,
+ 0xffaabb00,
+ 0,
+ 0,
+ sd_gfx_fb_get_width(fb),
+ sd_gfx_fb_get_height(fb));
+}
+
+static void test_card_fn(sd_gfx_card *card, void *fn_data, sd_gfx_card_event *ev) {
+ switch (ev->type) {
+ case SD_GFX_CARD_PIPE_WAKE_UP:
+ test_pipe_draw(ev->pipe);
+ break;
+ }
+}
+
+static void test_add(Test *t, sd_gfx_card *card) {
+ sd_gfx_card_set_event_fn(card, test_card_fn);
+}
+
+static void test_remove(Test *t, sd_gfx_card *card) {
+ sd_gfx_card_set_event_fn(card, NULL);
+}
+
+static void test_event_fn(sd_gfx_monitor *mon, void *fn_data, sd_gfx_monitor_event *ev) {
+ struct Test *t = fn_data;
+
+ switch (ev->type) {
+ case SD_GFX_MONITOR_CREATE:
+ if (ev->devtype == SD_GFX_DEV_CARD)
+ test_add(t, ev->card);
+ else
+ log_notice("unknown device of type %d", ev->devtype);
+ break;
+ case SD_GFX_MONITOR_DESTROY:
+ if (ev->devtype == SD_GFX_DEV_CARD)
+ test_remove(t, ev->card);
+ break;
+ default:
+ log_warning("unhandled monitor event: %d", ev->type);
+ break;
+ }
+}
+
+static int test_signal_fn(sd_event_source *s, const struct signalfd_siginfo *ssi, void *data) {
+ Test *t = data;
+
+ log_notice("catched signal %d, exiting..", (int)ssi->ssi_signo);
+ sd_event_request_quit(t->event);
+
+ return 0;
+}
+
+static int test_new(Test **out) {
+ static const int sigs[] = {
+ SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, 0
+ };
+ unsigned int flags;
+ sigset_t mask;
+ Test *t;
+ int r, i;
+
+ t = calloc(1, sizeof(*t));
+ if (!t)
+ return log_oom();
+
+ r = sd_event_default(&t->event);
+ if (r < 0) {
+ log_error("cannot get default event-loop: %d", r);
+ goto err_t;
+ }
+
+ sigemptyset(&mask);
+ for (i = 0; sigs[i]; ++i) {
+ sigaddset(&mask, sigs[i]);
+ r = sd_event_add_signal(t->event,
+ sigs[i],
+ test_signal_fn,
+ t,
+ &t->sigs[i]);
+ if (r < 0) {
+ log_error("cannot block signal %d: %d",
+ sigs[i], r);
+ goto err_sigs;
+ }
+ }
+ sigprocmask(SIG_BLOCK, &mask, NULL);
+
+ flags = SD_GFX_MONITOR_DEFAULT;
+ if (arg_all)
+ flags |= SD_GFX_MONITOR_IGNORE_SEATS;
+
+ r = sd_gfx_monitor_new(&t->mon,
+ SD_GFX_DEV_CARD,
+ flags,
+ t->event);
+ if (r < 0)
+ goto err_sigs;
+
+ sd_gfx_monitor_set_fn_data(t->mon, t);
+ sd_gfx_monitor_set_event_fn(t->mon, test_event_fn);
+ sd_gfx_monitor_parse_cmdline(t->mon);
+ if (arg_gpus)
+ sd_gfx_monitor_set_gpus(t->mon, arg_gpus);
+ if (arg_keymap)
+ sd_gfx_monitor_set_keymap(t->mon, arg_keymap);
+
+ *out = t;
+ return 0;
+
+err_sigs:
+ for (i = 0; t->sigs[i]; ++i)
+ sd_event_source_unref(t->sigs[i]);
+ sd_event_unref(t->event);
+err_t:
+ free(t);
+ return r;
+}
+
+static void test_free(Test *t) {
+ int i;
+
+ if (!t)
+ return;
+
+ sd_gfx_monitor_free(t->mon);
+ for (i = 0; t->sigs[i]; ++i)
+ sd_event_source_unref(t->sigs[i]);
+ sd_event_unref(t->event);
+ free(t);
+}
+
+static int test_run(Test *t) {
+ return sd_event_loop(t->event);
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] ...\n\n"
+ "sd-gfx graphics test.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --all Use all devices\n"
+ " --gpus=MATCH List of GPUs to use\n"
+ " --keymap=LMVO XKB keymap to use\n"
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_ALL,
+ ARG_GPUS,
+ ARG_KEYMAP,
+ };
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "all", no_argument, NULL, ARG_ALL },
+ { "gpus", required_argument, NULL, ARG_GPUS },
+ { "keymap", required_argument, NULL, ARG_KEYMAP },
+ {}
+ };
+ int c;
+
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+ switch(c) {
+ case 'h':
+ return help();
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0;
+ case ARG_ALL:
+ arg_all = true;
+ break;
+ case ARG_GPUS:
+ arg_gpus = optarg;
+ break;
+ case ARG_KEYMAP:
+ arg_keymap = optarg;
+ break;
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached("Unhandled option");
+ }
+ }
+
+ if (optind < argc) {
+ log_error("This program does not take arguments.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ Test *t = NULL;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+ setlocale(LC_ALL, "");
+
+ r = parse_argv(argc, argv);
+ if (r < 0)
+ return EXIT_FAILURE;
+ if (r == 0)
+ return EXIT_SUCCESS;
+
+ r = test_new(&t);
+ if (r < 0)
+ goto finish;
+
+ r = test_run(t);
+
+finish:
+ if (t)
+ test_free(t);
+
+ log_debug("exiting..");
+ return abs(r);
+}
--
1.8.4.2
David Herrmann
2013-11-27 18:48:42 UTC
Permalink
This adds the graphics-layer to sd-gfx. The graphics layer is based on
linux DRM+KMS. No other devices are supported (fbdev is obsolete!). The
API is rather complex but tries to hide as many DRM details as possible.

DRM divides each display-controller into cards. They are accessible via
/dev/dri/card<num>. Each card is independent of the others and you have to
create a separate sd_gfx_card for each of them. This is the based object
for DRM devices.

A single DRM card can drive many displays. The pipeline to drive a single
display can be quite complex (especially if multiple displays are run in
hardware clone-mode). On a single pipe/CRTC there may be many active
encoders, connectors, framebuffers, planes and more. The sd_gfx_card
objects hides most of this.

For each active pipe, sd_gfx_card advertises an sd_gfx_pipe to the
application. This is done on runtime to support monitor hotplugging. A
pipe is the main object applications deal with. It has an associated mode,
planes and framebuffers and an application should treat a pipe as a single
active connected monitor (although there might be multiple connectors in
clone-mode). A Wake-up event is sent when a pipe gets active (normally on
hotplug) and a Sleep event is sent when a pipe gets inactive. Furthermore,
on mode-change, session-change etc., the same is done. So applications can
treat WAKE_UP as "start rendering" and SLEEP as "stop rendering".

To render, applications need framebuffers. They are represented as
sd_gfx_fb. Framebuffers can be allocated by an application on request
(although, some cards may only provide a single framebuffer, applications
need to correctly deal with such cases). Direct memory-access to
framebuffers is hidden by sd_gfx_fb, but helpers are provided to
blit/blend data onto the framebuffer. Drivers may not allow direct
memory-access to framebuffers, so this needs to be hidden. More helpers
may be added once needed.

A framebuffer itself is useless as it is an off-screen buffer. To display
it, you need to attach it to a pipe. Each pipe can usually have only a
single backing framebuffer. However, hardware-blending/blitting may be
available, so another layer has to be introduced: planes

Planes are hardware entities which take a framebuffer and attach it to a
pipe. Each pipe has always one primary plane, which is the bottom-most
plane and the main entity used by applications. Simply attach your
framebuffer to the primary-plane and it will automatically get displayed
on the pipe.
However, some hardware provides additional planes. You can attach other
framebuffers to these planes and then attach the plane to a pipe. You can
specify offsets/sizes so the plane is scaled/moved before it is
blitted/blended over the primary plane. This allows hardware 2D
compositing.
Note that non-primary planes are not yet supported by the sd-gfx layer as
atomic-modesetting is not yet merged upstream.

Last but not least, we need to take vertical-blanks (VBLANK/VSYNC) into
account. Each active pipe always has an attached primary plane with an
attached front framebuffer. If you render into the front frameuffer, you
may see artifacts as not the whole screen is updated at once. Thus, you
should always allocate a second framebuffer to render into. Once your
frame is finished you swap both buffers so the whole screen is updated at
once. To avoid tearing, this swap must occur during a VBLANK. The
sd_gfx_plane object provides a swap/page-flip helper which schedules a
swap for the next VBLANK and provides an event to you once that happened.
---
Makefile.am | 1 +
configure.ac | 2 +-
src/libsystemd-gfx/gfx-drm.c | 2551 ++++++++++++++++++++++++++++++++++++++++++
src/systemd/sd-gfx.h | 195 ++++
4 files changed, 2748 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-gfx/gfx-drm.c

diff --git a/Makefile.am b/Makefile.am
index 6ecbd50..70148c5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3859,6 +3859,7 @@ noinst_LTLIBRARIES += \

libsystemd_gfx_la_SOURCES = \
src/libsystemd-gfx/sd-gfx.h \
+ src/libsystemd-gfx/gfx-drm.c \
src/libsystemd-gfx/gfx-kbd.c \
src/libsystemd-gfx/gfx-unifont.c

diff --git a/configure.ac b/configure.ac
index 74c37ae..b76a86d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -294,7 +294,7 @@ AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
have_gfx=no
AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
if test "x$enable_gfx" != "xno"; then
- PKG_CHECK_MODULES(GFX, [ libevdev >= 0.4 xkbcommon >= 0.3 ],
+ PKG_CHECK_MODULES(GFX, [ libdrm >= 2.4.45 libevdev >= 0.4 xkbcommon >= 0.3 ],
[AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built]) have_gfx=yes], have_gfx=no)
if test "x$have_gfx" = xno -a "x$enable_gfx" = xyes; then
AC_MSG_ERROR([*** sd-gfx support requested, but libraries not found])
diff --git a/src/libsystemd-gfx/gfx-drm.c b/src/libsystemd-gfx/gfx-drm.c
new file mode 100644
index 0000000..b0ce87f
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-drm.c
@@ -0,0 +1,2551 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <***@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include <drm.h>
+#include <drm_fourcc.h>
+
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+typedef struct gfx_config_pipe gfx_config_pipe;
+typedef struct gfx_connector gfx_connector;
+typedef struct gfx_encoder gfx_encoder;
+
+/* clock-wise framebuffer rotation */
+enum {
+ GFX_ROTATE_0,
+ GFX_ROTATE_90,
+ GFX_ROTATE_180,
+ GFX_ROTATE_270,
+};
+
+struct gfx_config_pipe {
+ unsigned int config_id;
+ char **connectors;
+ char *mode;
+ unsigned int rotate;
+
+ unsigned int disable : 1;
+};
+
+struct sd_gfx_fb {
+ unsigned long ref;
+ sd_gfx_plane *plane;
+ sd_gfx_fb_unlink_fn unlink_fn;
+ sd_gfx_fb_unpin_fn unpin_fn;
+ void *fn_data;
+ void *data;
+
+ uint32_t fb;
+ uint32_t format;
+ uint32_t handles[4];
+ uint32_t width;
+ uint32_t height;
+ uint32_t strides[4];
+ uint64_t size;
+ uint8_t *map;
+};
+
+struct sd_gfx_plane {
+ unsigned long ref;
+ sd_gfx_card *card;
+ sd_gfx_pipe *pipe;
+ void *data;
+ void *fn_data;
+ sd_gfx_plane_prepare_fn prepare_fn;
+ sd_gfx_plane_cancel_fn cancel_fn;
+ sd_gfx_plane_finish_fn finish_fn;
+ uint32_t plane_id;
+
+ size_t fb_count;
+ sd_gfx_fb **fbs;
+
+ sd_gfx_fb *current;
+ sd_gfx_fb *swap;
+ sd_gfx_fb *swap_to;
+
+ unsigned int primary : 1;
+ unsigned int enabled : 1;
+ unsigned int change : 1;
+};
+
+struct sd_gfx_mode {
+ unsigned long ref;
+ drmModeModeInfo info;
+};
+
+struct gfx_connector {
+ sd_gfx_card *card;
+ unsigned int id;
+ uint32_t connector_id;
+ const char *type;
+ uint32_t type_id;
+
+ /* kernel setup */
+ gfx_encoder *encoder;
+ uint32_t possible_encoders;
+ uint32_t possible_pipes;
+ unsigned int mode_count;
+ sd_gfx_mode **modes;
+ sd_gfx_mode *preferred_mode;
+ sd_gfx_mode *default_mode;
+ unsigned int dpms;
+ uint32_t dpms_prop;
+
+ /* connector setup */
+ sd_gfx_pipe *pipe;
+
+ /* wanted setup */
+ gfx_config_pipe *want_config;
+
+ /* state */
+ unsigned int assigned : 1;
+ unsigned int connected : 1;
+};
+
+struct gfx_encoder {
+ sd_gfx_card *card;
+ unsigned int id;
+ uint32_t encoder_id;
+
+ /* kernel setup */
+ sd_gfx_pipe *pipe;
+ uint32_t possible_pipes;
+ uint32_t possible_clones;
+};
+
+struct sd_gfx_pipe {
+ unsigned long ref;
+ sd_gfx_card *card;
+ unsigned int id;
+ sd_gfx_plane *primary;
+ void *data;
+ void *fn_data;
+
+ /* kernel setup */
+ drmModeCrtc *kern_crtc;
+ gfx_connector *kern_connector;
+ uint32_t crtc_id;
+ sd_gfx_mode *kern_mode;
+
+ /* pipe setup */
+ unsigned int rotation;
+ unsigned int connector_count;
+ gfx_connector **connectors;
+ uint32_t *connector_ids;
+ sd_gfx_mode *mode;
+
+ /* wanted setup */
+ unsigned int want_rotation;
+ unsigned int want_connector_count;
+ gfx_connector **want_connectors;
+ uint32_t *want_connector_ids;
+ sd_gfx_mode *want_mode;
+
+ /* page-flip counters */
+ uint64_t page_flip;
+ uint64_t page_flip_cnt;
+
+ /* state */
+ unsigned int assigned : 1;
+};
+
+struct sd_gfx_card {
+ char *name;
+ sd_gfx_card_event_fn event_fn;
+ void *data;
+ void *fn_data;
+ int fd;
+ sd_event *event;
+ sd_event_source *fd_source;
+
+ unsigned int pipe_ids;
+ sd_gfx_pipe **pipes;
+ unsigned int encoder_ids;
+ gfx_encoder **encoders;
+ unsigned int connector_ids;
+ gfx_connector **connectors;
+
+ gfx_connector **tcons;
+ gfx_config_pipe *configs;
+
+ unsigned int public : 1;
+ unsigned int awake : 1;
+ unsigned int real_awake : 1;
+ unsigned int tory : 1;
+ unsigned int clone_tory : 1;
+};
+
+static void gfx_card_async_sleep(sd_gfx_card *card);
+
+/*
+ * Page-flip data
+ * The DRM API allows us to store 64bit of data with each scheduled page-flip.
+ * The data is returned alongside the page-flip event. However, drivers tend to
+ * spuriously drop page-flip events if we reset CRTCs in between. Furthermore,
+ * on fast session-switches, page-flip events may be reported *after* we got
+ * reactivated, thus, we might have already scheduled a new page-flip.
+ * To reliable track page-flips, we use the 64bit arg to store the pipe-ID
+ * (5 bit) together with a 59bit counter. The counter allows to see whether a
+ * retrieved page-flip event is really the event we're waiting for or whether
+ * it was already superceeded by a following page-flip call.
+ */
+
+static uint64_t gfx_encode_arg(uint64_t arg, unsigned int id) {
+ return (arg << 5ULL) | (id & 0x1fULL);
+}
+
+static void gfx_decode_arg(uint64_t *arg, unsigned int *id) {
+ *id = *arg & 0x1fULL;
+ *arg >>= 5ULL;
+}
+
+/*
+ * Framebuffers
+ * Each FB represent a rectangular buffer with image data. A framebuffer may be
+ * a back buffer (currently not shown on screen) or a front-buffer (currently
+ * used as scanout-buffer). You should normally allocate at least two buffers
+ * so you never have to draw into a front-buffer (otherwise, tearing might
+ * occur).
+ *
+ * Framebuffers are always attached to a plane. Once a plane changes its mode,
+ * framebuffers might get orphaned and unlinked. So you should always react to
+ * plane-reconfigurations to retrieve the current framebuffers and screen
+ * sizes.
+ */
+
+static int gfx_find_slot(sd_gfx_plane *plane, sd_gfx_fb ***slot) {
+ sd_gfx_fb **t;
+ size_t num, i;
+
+ for (i = 0; i < plane->fb_count; ++i) {
+ if (!plane->fbs[i]) {
+ *slot = &plane->fbs[i];
+ return 0;
+ }
+ }
+
+ num = plane->fb_count ? plane->fb_count * 2 : 2;
+ if (num <= plane->fb_count)
+ return -ENOMEM;
+
+ t = realloc(plane->fbs, sizeof(*plane->fbs) * num);
+ if (!t)
+ return log_oom();
+
+ memset(&t[plane->fb_count], 0, sizeof(*plane->fbs) * (num - plane->fb_count));
+ *slot = &t[plane->fb_count];
+ plane->fb_count = num;
+ plane->fbs = t;
+
+ return 0;
+}
+
+int sd_gfx_fb_new(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t handles[4],
+ uint32_t width,
+ uint32_t height,
+ uint32_t strides[4],
+ uint32_t offsets[4],
+ uint64_t size,
+ uint64_t map_offset) {
+ sd_gfx_fb *fb, **slot = NULL;
+ int r;
+
+ assert(out);
+ assert(plane);
+ assert(handles);
+ assert(strides);
+ assert(offsets);
+
+ if (!plane->card)
+ return -EINVAL;
+
+ r = gfx_find_slot(plane, &slot);
+ if (r < 0)
+ return r;
+
+ fb = calloc(1, sizeof(*fb));
+ if (!fb)
+ return log_oom();
+
+ fb->ref = 1;
+ fb->plane = plane;
+
+ fb->format = format;
+ memcpy(fb->handles, handles, sizeof(fb->handles));
+ fb->width = width;
+ fb->height = height;
+ memcpy(fb->strides, strides, sizeof(fb->strides));
+ fb->size = size;
+
+ r = drmModeAddFB2(plane->card->fd,
+ fb->width,
+ fb->height,
+ fb->format,
+ fb->handles,
+ fb->strides,
+ offsets,
+ &fb->fb, 0);
+ if (r < 0) {
+ log_error("card %s: AddFB2(%dx%d@%d): %m",
+ plane->card->name, (int)fb->width, (int)fb->height, (int)fb->format);
+ r = -EINVAL;
+ goto err_free;
+ }
+
+ if (map_offset) {
+ fb->map = mmap(0, fb->size, PROT_WRITE, MAP_SHARED, plane->card->fd, map_offset);
+ if (fb->map == MAP_FAILED) {
+ log_error("card %s: mmap(%llu): %m",
+ plane->card->name, (unsigned long long)map_offset);
+ r = -EINVAL;
+ goto err_fb;
+ }
+ memset(fb->map, 0, fb->size);
+ }
+
+ if (!plane->change) {
+ sd_gfx_fb_ref(fb);
+ *slot = fb;
+ }
+
+ *out = fb;
+ return 0;
+
+err_fb:
+ drmModeRmFB(plane->card->fd, fb->fb);
+err_free:
+ free(fb);
+ return r;
+}
+
+int sd_gfx_fb_new_rgb(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t handle,
+ uint32_t width,
+ uint32_t height,
+ uint32_t stride,
+ uint32_t offset,
+ uint64_t size,
+ uint64_t map_offset) {
+ uint32_t handles[4], strides[4], offsets[4];
+
+ memset(handles, 0, sizeof(handles));
+ memset(strides, 0, sizeof(strides));
+ memset(offsets, 0, sizeof(offsets));
+ handles[0] = handle;
+ strides[0] = stride;
+ offsets[0] = offset;
+
+ return sd_gfx_fb_new(out, plane, format, handles, width, height, strides, offsets, size, map_offset);
+}
+
+static void gfx_fb_unlink_dumb_fn(sd_gfx_fb *fb, void *fn_data) {
+ struct drm_mode_destroy_dumb dreq;
+
+ memset(&dreq, 0, sizeof(dreq));
+ dreq.handle = fb->handles[0];
+ drmIoctl(fb->plane->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+}
+
+int sd_gfx_fb_new_dumb(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t bpp,
+ uint32_t width,
+ uint32_t height) {
+ sd_gfx_fb *fb;
+ struct drm_mode_create_dumb creq;
+ struct drm_mode_destroy_dumb dreq;
+ struct drm_mode_map_dumb mreq;
+ int r;
+
+ assert(out);
+ assert(plane);
+
+ if (!plane->card)
+ return -EINVAL;
+
+ memset(&creq, 0, sizeof(creq));
+ creq.width = width;
+ creq.height = height;
+ creq.bpp = bpp;
+
+ r = drmIoctl(plane->card->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+ if (r < 0) {
+ log_error("card %s: CreateDumb(%dx%***@32): %m",
+ plane->card->name, (int)creq.width, (int)creq.height);
+ return -EINVAL;
+ }
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.handle = creq.handle;
+
+ r = drmIoctl(plane->card->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+ if (r < 0) {
+ log_error("card %s: MapDumb(%d): %m",
+ plane->card->name, (int)creq.handle);
+ r = -EINVAL;
+ goto err_dumb;
+ }
+
+ r = sd_gfx_fb_new_rgb(&fb,
+ plane,
+ format,
+ creq.handle,
+ creq.width,
+ creq.height,
+ creq.pitch,
+ 0,
+ creq.size,
+ mreq.offset);
+ if (r < 0)
+ goto err_dumb;
+
+ fb->unlink_fn = gfx_fb_unlink_dumb_fn;
+ *out = fb;
+ return 0;
+
+err_dumb:
+ memset(&dreq, 0, sizeof(dreq));
+ dreq.handle = creq.handle;
+ drmIoctl(plane->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return r;
+}
+
+void sd_gfx_fb_ref(sd_gfx_fb *fb) {
+ if (!fb || !fb->ref)
+ return;
+
+ ++fb->ref;
+}
+
+static void gfx_fb_unlink(sd_gfx_fb *fb) {
+ if (!fb->plane)
+ return;
+
+ if (fb->map)
+ munmap(fb->map, fb->size);
+ fb->map = NULL;
+
+ drmModeRmFB(fb->plane->card->fd, fb->fb);
+
+ if (fb->unlink_fn)
+ fb->unlink_fn(fb, fb->fn_data);
+
+ fb->plane = NULL;
+}
+
+static void gfx_fb_unpin(sd_gfx_fb *fb) {
+ if (fb->unpin_fn)
+ fb->unpin_fn(fb, fb->fn_data);
+}
+
+void sd_gfx_fb_unref(sd_gfx_fb *fb) {
+ if (!fb || !fb->ref || --fb->ref)
+ return;
+
+ gfx_fb_unlink(fb);
+ free(fb);
+}
+
+void sd_gfx_fb_set_data(sd_gfx_fb *fb, void *data) {
+ fb->data = data;
+}
+
+void *sd_gfx_fb_get_data(sd_gfx_fb *fb) {
+ return fb->data;
+}
+
+void sd_gfx_fb_set_fn_data(sd_gfx_fb *fb, void *fn_data) {
+ fb->fn_data = fn_data;
+}
+
+void *sd_gfx_fb_get_fn_data(sd_gfx_fb *fb) {
+ return fb->fn_data;
+}
+
+void sd_gfx_fb_set_fns(sd_gfx_fb *fb, sd_gfx_fb_unlink_fn unlink_fn, sd_gfx_fb_unpin_fn unpin_fn) {
+ fb->unlink_fn = unlink_fn;
+ fb->unpin_fn = unpin_fn;
+}
+
+sd_gfx_plane *sd_gfx_fb_get_plane(sd_gfx_fb *fb) {
+ return fb->plane;
+}
+
+uint32_t sd_gfx_fb_get_format(sd_gfx_fb *fb) {
+ return fb->format;
+}
+
+uint32_t sd_gfx_fb_get_width(sd_gfx_fb *fb) {
+ return fb->width;
+}
+
+uint32_t sd_gfx_fb_get_height(sd_gfx_fb *fb) {
+ return fb->height;
+}
+
+void sd_gfx_fb_fill(sd_gfx_fb *fb,
+ uint32_t color,
+ unsigned int pos_x,
+ unsigned int pos_y,
+ unsigned int width,
+ unsigned int height) {
+ uint8_t *dst;
+ unsigned int i;
+ uint32_t *d;
+
+ if (!fb->plane || !fb->map)
+ return;
+ if (pos_x >= fb->width || pos_y >= fb->height)
+ return;
+ if (!width || !height)
+ return;
+
+ width = MIN(width, fb->width - pos_x);
+ height = MIN(height, fb->height - pos_y);
+
+ dst = fb->map + pos_y * fb->strides[0] + pos_x * 4;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ d = (void*)&dst[i * 4];
+ *d = color;
+ }
+
+ dst += fb->strides[0];
+ }
+}
+
+void sd_gfx_fb_blend_bichrome(sd_gfx_fb *fb,
+ uint32_t fg_color,
+ uint32_t bg_color,
+ unsigned int pos_x,
+ unsigned int pos_y,
+ const sd_gfx_buffer *source) {
+ unsigned int width, height;
+ const uint8_t *src, *s;
+ uint8_t *dst;
+ unsigned int i;
+ uint32_t *d;
+
+ if (!fb->plane || !fb->map)
+ return;
+ if (pos_x >= fb->width || pos_y >= fb->height)
+ return;
+ if (!source->width || !source->height)
+ return;
+
+ /* TODO: add rotation support */
+ /* TODO: add support for more formats */
+ if (source->format != SD_GFX_BUFFER_FORMAT_A1)
+ return;
+
+ width = MIN(source->width, fb->width - pos_x);
+ height = MIN(source->height, fb->height - pos_y);
+
+ src = source->data;
+ dst = fb->map + pos_y * fb->strides[0] + pos_x * 4;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ s = (void*)&src[i / 8];
+ d = (void*)&dst[i * 4];
+ if (*s & (1 << (7 - i % 8)))
+ *d = fg_color;
+ else
+ *d = bg_color;
+ }
+
+ src += source->stride;
+ dst += fb->strides[0];
+ }
+}
+
+/*
+ * Planes
+ * Each plane allows to map framebuffers into the virtual screen of a pipe.
+ * Normally, each pipe has one primary plane which is the underlying
+ * framebuffer. But many new hardware provides additional planes so we can do
+ * blitting or even blending of multiple buffers in hardware.
+ */
+
+static int gfx_plane_new(sd_gfx_plane **out, sd_gfx_card *card) {
+ sd_gfx_plane *plane;
+
+ plane = calloc(1, sizeof(*plane));
+ if (!plane)
+ return log_oom();
+
+ plane->ref = 1;
+ plane->card = card;
+
+ *out = plane;
+ return 0;
+}
+
+static int gfx_plane_new_primary(sd_gfx_plane **out, sd_gfx_pipe *pipe) {
+ sd_gfx_plane *plane = NULL;
+ int r;
+
+ r = gfx_plane_new(&plane, pipe->card);
+ if (r < 0)
+ return r;
+
+ plane->primary = 1;
+ plane->pipe = pipe;
+
+ *out = plane;
+ return 0;
+}
+
+void sd_gfx_plane_ref(sd_gfx_plane *plane) {
+ if (!plane || !plane->ref)
+ return;
+
+ ++plane->ref;
+}
+
+static void gfx_plane_unlink(sd_gfx_plane *plane) {
+ unsigned int i;
+
+ if (!plane->card)
+ return;
+
+ if (plane->current)
+ gfx_fb_unpin(plane->current);
+ if (plane->swap_to)
+ gfx_fb_unpin(plane->swap_to);
+ if (plane->swap)
+ gfx_fb_unpin(plane->swap);
+
+ for (i = 0; i < plane->fb_count; ++i) {
+ if (plane->fbs[i]) {
+ gfx_fb_unlink(plane->fbs[i]);
+ sd_gfx_fb_unref(plane->fbs[i]);
+ plane->fbs[i] = NULL;
+ }
+ }
+
+ plane->current = NULL;
+ plane->swap = NULL;
+ plane->swap_to = NULL;
+ plane->pipe = NULL;
+ plane->card = NULL;
+}
+
+void sd_gfx_plane_unref(sd_gfx_plane *plane) {
+ if (!plane || !plane->ref || --plane->ref)
+ return;
+
+ gfx_plane_unlink(plane);
+ free(plane->fbs);
+ free(plane);
+}
+
+static int gfx_plane_prepare_change(sd_gfx_plane *plane, unsigned int width, unsigned int height, sd_gfx_fb **fb) {
+ int r;
+
+ if (plane->current) {
+ if (plane->current->width == width && plane->current->height == height) {
+ *fb = plane->current;
+ return 0;
+ }
+ }
+
+ plane->change = 1;
+ if (plane->prepare_fn)
+ r = plane->prepare_fn(plane, plane->fn_data, width, height, fb);
+ else
+ r = sd_gfx_fb_new_dumb(fb, plane, DRM_FORMAT_XRGB8888, 32, width, height);
+ plane->change = 0;
+
+ return r;
+}
+
+static void gfx_plane_cancel_change(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+ if (fb != plane->current) {
+ if (plane->cancel_fn)
+ plane->cancel_fn(plane, plane->fn_data, fb);
+ gfx_fb_unlink(fb);
+ sd_gfx_fb_unref(fb);
+ }
+}
+
+static void gfx_plane_finish_change(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+ unsigned int i;
+
+ if (fb == plane->current)
+ return;
+
+ if (plane->current)
+ gfx_fb_unpin(plane->current);
+ if (plane->swap_to)
+ gfx_fb_unpin(plane->swap_to);
+ if (plane->swap)
+ gfx_fb_unpin(plane->swap);
+ plane->current = NULL;
+ plane->swap = NULL;
+ plane->swap_to = NULL;
+
+ for (i = 0; i < plane->fb_count; ++i) {
+ if (plane->fbs[i]) {
+ gfx_fb_unlink(plane->fbs[i]);
+ sd_gfx_fb_unref(plane->fbs[i]);
+ plane->fbs[i] = NULL;
+ }
+ }
+
+ plane->fbs[0] = fb;
+ plane->current = fb;
+ if (plane->finish_fn)
+ plane->finish_fn(plane, plane->fn_data, fb);
+}
+
+void sd_gfx_plane_set_data(sd_gfx_plane *plane, void *data) {
+ plane->data = data;
+}
+
+void *sd_gfx_plane_get_data(sd_gfx_plane *plane) {
+ return plane->data;
+}
+
+void sd_gfx_plane_set_fn_data(sd_gfx_plane *plane, void *fn_data) {
+ plane->fn_data = fn_data;
+}
+
+void *sd_gfx_plane_get_fn_data(sd_gfx_plane *plane) {
+ return plane->fn_data;
+}
+
+void sd_gfx_plane_set_fns(sd_gfx_plane *plane,
+ sd_gfx_plane_prepare_fn prepare_fn,
+ sd_gfx_plane_cancel_fn cancel_fn,
+ sd_gfx_plane_finish_fn finish_fn) {
+ plane->prepare_fn = prepare_fn;
+ plane->cancel_fn = cancel_fn;
+ plane->finish_fn = finish_fn;
+}
+
+unsigned int sd_gfx_plane_get_id(sd_gfx_plane *plane) {
+ return plane->plane_id;
+}
+
+sd_gfx_card *sd_gfx_plane_get_card(sd_gfx_plane *plane) {
+ return plane->card;
+}
+
+sd_gfx_pipe *sd_gfx_plane_get_pipe(sd_gfx_plane *plane) {
+ return plane->pipe;
+}
+
+bool sd_gfx_plane_is_enabled(sd_gfx_plane *plane) {
+ return plane->primary ? 1 : plane->enabled;
+}
+
+void sd_gfx_plane_enable(sd_gfx_plane *plane) {
+ plane->enabled = 1;
+}
+
+void sd_gfx_plane_disable(sd_gfx_plane *plane) {
+ if (!plane->primary)
+ plane->enabled = 0;
+}
+
+bool sd_gfx_plane_supports_format(sd_gfx_plane *plane, uint32_t format) {
+ if (plane->primary) {
+ if (format == DRM_FORMAT_XRGB8888)
+ return true;
+ else
+ return false;
+ }
+
+ return false;
+}
+
+bool sd_gfx_plane_supports_pipe(sd_gfx_plane *plane, sd_gfx_pipe *pipe) {
+ if (plane->pipe == pipe)
+ return true;
+ if (plane->primary)
+ return false;
+
+ return false;
+}
+
+sd_gfx_fb *sd_gfx_plane_get_front(sd_gfx_plane *plane) {
+ return plane->current;
+}
+
+sd_gfx_fb *sd_gfx_plane_get_back(sd_gfx_plane *plane) {
+ sd_gfx_fb *fb;
+ unsigned int i;
+
+ for (i = 0; i < plane->fb_count; ++i) {
+ fb = plane->fbs[i];
+ if (fb && fb != plane->current && fb != plane->swap_to && fb != plane->swap)
+ return fb;
+ }
+
+ return NULL;
+}
+
+void sd_gfx_plane_swap_to(sd_gfx_plane *plane, sd_gfx_fb *fb) {
+ if (!plane->card)
+ return;
+ if (fb && fb->plane != plane)
+ return;
+
+ if (plane->swap_to)
+ gfx_fb_unpin(plane->swap_to);
+
+ plane->swap_to = fb;
+}
+
+static void gfx_plane_swap(sd_gfx_plane *plane) {
+ gfx_fb_unpin(plane->current);
+ plane->current = plane->swap;
+ plane->swap = NULL;
+}
+
+/*
+ * Modes
+ * DRM modes describe the resolution, refresh-rate and other flags of a
+ * configured pipe. They are immutable and dead. They only serve to forward
+ * mode-information to the caller.
+ */
+
+static int gfx_mode_new(sd_gfx_mode **out, drmModeModeInfo *info) {
+ sd_gfx_mode *mode;
+
+ mode = calloc(1, sizeof(*mode));
+ if (!mode)
+ return log_oom();
+
+ mode->ref = 1;
+ mode->info = *info;
+
+ *out = mode;
+ return 0;
+}
+
+void sd_gfx_mode_ref(sd_gfx_mode *mode) {
+ if (!mode || !mode->ref)
+ return;
+
+ ++mode->ref;
+}
+
+void sd_gfx_mode_unref(sd_gfx_mode *mode) {
+ if (!mode || !mode->ref || --mode->ref)
+ return;
+
+ free(mode);
+}
+
+unsigned int sd_gfx_mode_get_width(sd_gfx_mode *mode) {
+ return mode->info.hdisplay;
+}
+
+unsigned int sd_gfx_mode_get_height(sd_gfx_mode *mode) {
+ return mode->info.vdisplay;
+}
+
+const char *sd_gfx_mode_get_name(sd_gfx_mode *mode) {
+ return mode->info.name;
+}
+
+/*
+ * Connectors
+ * Each physical connector on your card is represented as a gfx_connector. A
+ * card may not be able to program all connectors at the same time (eg., if
+ * not enough pipes are available). Thus, we need to find a suitable
+ * configuration for all requested connectors so we can program them the way
+ * the user wants.
+ * Connectors are normally the only entity that is known to users, and it's the
+ * only entity that should be configurable by users. The whole underlying
+ * pipeline should be programmed internally without ever telling users about
+ * it.
+ */
+
+static const char *gfx_connector_types[] = {
+ [DRM_MODE_CONNECTOR_Unknown] = "Unknown",
+ [DRM_MODE_CONNECTOR_VGA] = "VGA",
+ [DRM_MODE_CONNECTOR_DVII] = "DVI-I",
+ [DRM_MODE_CONNECTOR_DVID] = "DVI-D",
+ [DRM_MODE_CONNECTOR_DVIA] = "DVI-A",
+ [DRM_MODE_CONNECTOR_Composite] = "Composite",
+ [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO",
+ [DRM_MODE_CONNECTOR_LVDS] = "LVDS",
+ [DRM_MODE_CONNECTOR_Component] = "Component",
+ [DRM_MODE_CONNECTOR_9PinDIN] = "DIN",
+ [DRM_MODE_CONNECTOR_DisplayPort] = "DP",
+ [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A",
+ [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B",
+ [DRM_MODE_CONNECTOR_TV] = "TV",
+ [DRM_MODE_CONNECTOR_eDP] = "eDP",
+};
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeConnector*, drmModeFreeConnector);
+
+static void gfx_connector_refresh(gfx_connector *connector, drmModeConnector *con) {
+ _cleanup_(drmModeFreeConnectorp) drmModeConnector *con2 = NULL;
+ drmModePropertyRes *prop;
+ unsigned int i;
+ int j;
+ gfx_encoder *encoder;
+
+ connector->encoder = NULL;
+
+ if (!con) {
+ con2 = drmModeGetConnector(connector->card->fd, connector->connector_id);
+ if (!con2) {
+ log_error("card %s: GetConnector(%d): %m",
+ connector->card->name, (int)connector->connector_id);
+ return;
+ }
+ con = con2;
+ }
+
+ if (con->encoder_id > 0) {
+ for (i = 0; i < connector->card->encoder_ids; ++i) {
+ encoder = connector->card->encoders[i];
+ if (encoder && encoder->encoder_id == con->encoder_id) {
+ connector->encoder = encoder;
+ break;
+ }
+ }
+ }
+
+ for (j = 0; j < con->count_props; ++j) {
+ prop = drmModeGetProperty(connector->card->fd, con->props[j]);
+ if (!prop)
+ continue;
+
+ if (!strcmp(prop->name, "DPMS")) {
+ connector->dpms_prop = prop->prop_id;
+ connector->dpms = con->prop_values[j];
+ }
+
+ drmModeFreeProperty(prop);
+ }
+
+ if (connector->card->real_awake && connector->encoder && connector->encoder->pipe)
+ if (!connector->encoder->pipe->kern_connector)
+ connector->encoder->pipe->kern_connector = connector;
+}
+
+static int gfx_connector_new(gfx_connector **out, sd_gfx_card *card, drmModeConnector *con) {
+ gfx_connector *connector;
+ gfx_encoder *encoder;
+ sd_gfx_mode *mode;
+ unsigned int j;
+ int i, r;
+
+ connector = calloc(1, sizeof(*connector));
+ if (!connector)
+ return log_oom();
+
+ connector->card = card;
+ connector->id = card->connector_ids;
+ connector->connector_id = con->connector_id;
+ connector->connected = con->connection == DRM_MODE_CONNECTED;
+ connector->dpms = SD_GFX_DPMS_UNKNOWN;
+
+ if (con->connector_type < ELEMENTSOF(gfx_connector_types))
+ connector->type = gfx_connector_types[con->connector_type];
+ if (!connector->type)
+ connector->type = gfx_connector_types[DRM_MODE_CONNECTOR_Unknown];
+
+ connector->type_id = con->connector_type_id;
+
+ connector->modes = calloc(con->count_modes, sizeof(*connector->modes));
+ if (!connector->modes) {
+ free(connector);
+ return log_oom();
+ }
+
+ for (i = 0; i < con->count_modes; ++i) {
+ r = gfx_mode_new(&mode, &con->modes[i]);
+ if (r >= 0) {
+ connector->modes[connector->mode_count++] = mode;
+ if (!connector->preferred_mode && (mode->info.type & DRM_MODE_TYPE_PREFERRED))
+ connector->preferred_mode = mode;
+ if (!connector->default_mode && (mode->info.type & DRM_MODE_TYPE_DEFAULT))
+ connector->default_mode = mode;
+ }
+ }
+
+ if (connector->mode_count > 0) {
+ if (!connector->preferred_mode)
+ connector->preferred_mode = connector->modes[0];
+ if (!connector->default_mode)
+ connector->default_mode = connector->preferred_mode;
+ }
+
+ connector->possible_pipes = 0xffffffff;
+ for (j = 0; j < card->encoder_ids; ++j) {
+ encoder = card->encoders[j];
+ if (!encoder)
+ continue;
+
+ for (i = 0; i < con->count_encoders; ++i) {
+ if (encoder->encoder_id == con->encoders[i]) {
+ connector->possible_encoders |= 1 << j;
+ connector->possible_pipes &= encoder->possible_pipes;
+ }
+ }
+ }
+
+ gfx_connector_refresh(connector, con);
+
+ *out = connector;
+ return 0;
+}
+
+static void gfx_connector_free(gfx_connector *connector) {
+ unsigned int i;
+
+ if (!connector)
+ return;
+
+ for (i = 0; i < connector->mode_count; ++i)
+ sd_gfx_mode_unref(connector->modes[i]);
+
+ free(connector->modes);
+ free(connector);
+}
+
+static bool gfx_connector_match(gfx_connector *connector, const char *name) {
+ size_t len;
+ unsigned int id;
+
+ len = strlen(connector->type);
+ if (strncasecmp(name, connector->type, len))
+ return false;
+ if (!name[len])
+ return connector->type_id == 1;
+ if (name[len] != '-')
+ return false;
+ if (safe_atou(&name[len + 1], &id) < 0)
+ return false;
+
+ return connector->type_id == id;
+}
+
+static void gfx_connector_set_dpms(gfx_connector *connector, unsigned int dpms) {
+ int r;
+
+ if (!connector->dpms_prop)
+ return;
+
+ r = drmModeConnectorSetProperty(connector->card->fd, connector->connector_id, connector->dpms_prop, dpms);
+ if (r >= 0)
+ connector->dpms = dpms;
+}
+
+/*
+ * Encoders
+ * DRM encoders describe the pipeline from a crtc to a connector. They are only
+ * used to tell user-space about pipeline-constraints. We cannot explicitly
+ * configure any encoder.
+ */
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeEncoder*, drmModeFreeEncoder);
+
+static void gfx_encoder_refresh(gfx_encoder *encoder, drmModeEncoder *enc) {
+ _cleanup_(drmModeFreeEncoderp) drmModeEncoder *enc2 = NULL;
+ unsigned int i;
+ sd_gfx_pipe *pipe;
+
+ encoder->pipe = NULL;
+
+ if (!enc) {
+ enc2 = drmModeGetEncoder(encoder->card->fd, encoder->encoder_id);
+ if (!enc2) {
+ log_error("card %s: GetEncoder(%d): %m",
+ encoder->card->name, (int)encoder->encoder_id);
+ return;
+ }
+ enc = enc2;
+ }
+
+ if (enc->crtc_id > 0) {
+ for (i = 0; i < encoder->card->pipe_ids; ++i) {
+ pipe = encoder->card->pipes[i];
+ if (!pipe)
+ continue;
+
+ if (pipe->crtc_id == enc->crtc_id) {
+ encoder->pipe = pipe;
+ break;
+ }
+ }
+ }
+}
+
+static int gfx_encoder_new(gfx_encoder **out, sd_gfx_card *card, drmModeEncoder *enc) {
+ gfx_encoder *encoder;
+
+ encoder = calloc(1, sizeof(*encoder));
+ if (!encoder)
+ return log_oom();
+
+ encoder->card = card;
+ encoder->id = card->encoder_ids;
+ encoder->encoder_id = enc->encoder_id;
+ encoder->possible_pipes = enc->possible_crtcs;
+ encoder->possible_clones = enc->possible_clones;
+
+ gfx_encoder_refresh(encoder, enc);
+
+ *out = encoder;
+ return 0;
+}
+
+static void gfx_encoder_free(gfx_encoder *encoder) {
+ if (!encoder)
+ return;
+
+ free(encoder);
+}
+
+/*
+ * Pipes
+ * A pipe (or CRTC) is the central mode-setting object. It controls which
+ * planes, which framebuffers, which encoders and connectors to use. A pipe is
+ * usually a piece of hardware and has several constraints we need to respect
+ * during modesetting.
+ *
+ * On each pipe we can only set a single framebuffer. Thus, if multiple
+ * connectors are bound to a single pipe, they will mirror each other. The
+ * kernel automatically figures out which encoders to use to drive a given set
+ * of connectors on a single pipe.
+ *
+ * Each pipe has a primary plane. Newer hardware allows to assign additional
+ * planes to a pipe, thus allowing 2D hardware compositing. The kernel usually
+ * reserves one plane for hardware-cursors.
+ *
+ * Modesetting should be atomic. That means, all state should be applied at the
+ * same time and preferably during a vertical blank (to avoid tearing).
+ * Depending on the kernel API and driver support, this might not be supported.
+ * However, our API emulates atomic modesetting for all other devices and simply
+ * applies all state sequencially.
+ * This means, if you change any state on the connectors or planes, it will not
+ * get applied until you commit it. The pipe decides whether a full modeset is
+ * performed or whether we can schedule a cheap page-flip instead.
+ */
+
+static void gfx_pipe_call(sd_gfx_pipe *pipe, unsigned int type) {
+ struct sd_gfx_card_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.pipe = pipe;
+
+ if (pipe->card->event_fn)
+ pipe->card->event_fn(pipe->card, pipe->card->fn_data, &event);
+}
+
+static void gfx_pipe_call_wake_up(sd_gfx_pipe *pipe, sd_gfx_mode *old_mode) {
+ struct sd_gfx_card_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = SD_GFX_CARD_PIPE_WAKE_UP;
+ event.pipe = pipe;
+ event.old_mode = old_mode;
+
+ if (pipe->card->event_fn)
+ pipe->card->event_fn(pipe->card, pipe->card->fn_data, &event);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeCrtc*, drmModeFreeCrtc);
+
+static void gfx_pipe_refresh(sd_gfx_pipe *pipe, drmModeCrtc *crtc) {
+ _cleanup_(drmModeFreeCrtcp) drmModeCrtc *crtc2 = NULL;
+
+ sd_gfx_mode_unref(pipe->kern_mode);
+ pipe->kern_mode = NULL;
+
+ if (!crtc) {
+ crtc2 = drmModeGetCrtc(pipe->card->fd, pipe->crtc_id);
+ if (!crtc2) {
+ log_error("card %s: GetCrtc(%d): %m",
+ pipe->card->name, (int)pipe->crtc_id);
+ return;
+ }
+ crtc = crtc2;
+ }
+
+ if (crtc->mode_valid) {
+ /* keeps NULL on failure */
+ gfx_mode_new(&pipe->kern_mode, &crtc->mode);
+ }
+
+ if (pipe->card->real_awake && !pipe->kern_crtc && crtc->buffer_id) {
+ if (crtc2) {
+ pipe->kern_crtc = crtc2;
+ crtc2 = NULL;
+ } else {
+ pipe->kern_crtc = drmModeGetCrtc(pipe->card->fd, pipe->crtc_id);
+ if (pipe->kern_crtc && !pipe->kern_crtc->buffer_id) {
+ drmModeFreeCrtc(pipe->kern_crtc);
+ pipe->kern_crtc = NULL;
+ }
+ }
+ }
+}
+
+static int gfx_pipe_new(sd_gfx_pipe **out, sd_gfx_card *card, drmModeCrtc *crtc, unsigned int connector_count) {
+ sd_gfx_pipe *pipe;
+ int r;
+
+ pipe = calloc(1, sizeof(*pipe));
+ if (!pipe)
+ return log_oom();
+
+ pipe->ref = 1;
+ pipe->card = card;
+ pipe->id = card->pipe_ids;
+ pipe->crtc_id = crtc->crtc_id;
+ pipe->page_flip_cnt = 1;
+
+ pipe->connectors = calloc(connector_count, sizeof(*pipe->connectors));
+ if (!pipe->connectors) {
+ r = log_oom();
+ goto err_pipe;
+ }
+
+ pipe->want_connectors = calloc(connector_count, sizeof(*pipe->want_connectors));
+ if (!pipe->want_connectors) {
+ r = log_oom();
+ goto err_connectors;
+ }
+
+ pipe->connector_ids = calloc(connector_count, sizeof(*pipe->connector_ids));
+ if (!pipe->connector_ids) {
+ r = log_oom();
+ goto err_want_connectors;
+ }
+
+ pipe->want_connector_ids = calloc(connector_count, sizeof(*pipe->want_connector_ids));
+ if (!pipe->want_connector_ids) {
+ r = log_oom();
+ goto err_connector_ids;
+ }
+
+ r = gfx_plane_new_primary(&pipe->primary, pipe);
+ if (r < 0)
+ goto err_want_connector_ids;
+
+ gfx_pipe_refresh(pipe, crtc);
+
+ *out = pipe;
+ return 0;
+
+err_want_connector_ids:
+ free(pipe->want_connector_ids);
+err_connector_ids:
+ free(pipe->connector_ids);
+err_want_connectors:
+ free(pipe->want_connectors);
+err_connectors:
+ free(pipe->connectors);
+err_pipe:
+ free(pipe);
+ return r;
+}
+
+void sd_gfx_pipe_ref(sd_gfx_pipe *pipe) {
+ if (!pipe || !pipe->ref)
+ return;
+
+ ++pipe->ref;
+}
+
+static void gfx_pipe_unlink(sd_gfx_pipe *pipe) {
+ if (!pipe->card)
+ return;
+
+ if (pipe->card->public) {
+ if (pipe->assigned && pipe->mode)
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_DESTROY);
+ }
+
+ pipe->assigned = 0;
+
+ drmModeFreeCrtc(pipe->kern_crtc);
+ pipe->kern_crtc = NULL;
+
+ pipe->connector_count = 0;
+
+ sd_gfx_mode_unref(pipe->mode);
+ pipe->mode = NULL;
+ sd_gfx_mode_unref(pipe->want_mode);
+ pipe->want_mode = NULL;
+ sd_gfx_mode_unref(pipe->kern_mode);
+ pipe->kern_mode = NULL;
+
+ gfx_plane_unlink(pipe->primary);
+ sd_gfx_plane_unref(pipe->primary);
+ pipe->primary = NULL;
+
+ pipe->card = NULL;
+}
+
+void sd_gfx_pipe_unref(sd_gfx_pipe *pipe) {
+ if (!pipe || !pipe->ref || --pipe->ref)
+ return;
+
+ gfx_pipe_unlink(pipe);
+ free(pipe->want_connector_ids);
+ free(pipe->connector_ids);
+ free(pipe->want_connectors);
+ free(pipe->connectors);
+ free(pipe);
+}
+
+void sd_gfx_pipe_set_data(sd_gfx_pipe *pipe, void *data) {
+ pipe->data = data;
+}
+
+void *sd_gfx_pipe_get_data(sd_gfx_pipe *pipe) {
+ return pipe->data;
+}
+
+void sd_gfx_pipe_set_fn_data(sd_gfx_pipe *pipe, void *fn_data) {
+ pipe->fn_data = fn_data;
+}
+
+void *sd_gfx_pipe_get_fn_data(sd_gfx_pipe *pipe) {
+ return pipe->fn_data;
+}
+
+unsigned int sd_gfx_pipe_get_id(sd_gfx_pipe *pipe) {
+ return pipe->id;
+}
+
+unsigned int sd_gfx_pipe_get_dpms(sd_gfx_pipe *pipe) {
+ unsigned int i, res;
+
+ if (!pipe->card || !pipe->card->real_awake)
+ return SD_GFX_DPMS_UNKNOWN;
+
+ res = SD_GFX_DPMS_ON;
+ for (i = 0; i < pipe->connector_count; ++i)
+ if (pipe->connectors[i]->dpms > res)
+ res = pipe->connectors[i]->dpms;
+
+ return res;
+}
+
+void sd_gfx_pipe_set_dpms(sd_gfx_pipe *pipe, unsigned int dpms) {
+ unsigned int i;
+
+ if (!pipe->card || dpms >= SD_GFX_DPMS_UNKNOWN)
+ return;
+
+ for (i = 0; i < pipe->connector_count; ++i)
+ gfx_connector_set_dpms(pipe->connectors[i], dpms);
+}
+
+sd_gfx_mode *sd_gfx_pipe_get_mode(sd_gfx_pipe *pipe) {
+ return pipe->mode;
+}
+
+sd_gfx_plane *sd_gfx_pipe_get_cursor(sd_gfx_pipe *pipe) {
+ return NULL;
+}
+
+sd_gfx_plane *sd_gfx_pipe_get_primary_plane(sd_gfx_pipe *pipe) {
+ return pipe->primary;
+}
+
+int sd_gfx_pipe_attach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane) {
+ if (!pipe->card)
+ return -ENODEV;
+
+ return -EINVAL;
+}
+
+void sd_gfx_pipe_detach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane) {
+}
+
+static void gfx_pipe_commit_kern(sd_gfx_pipe *pipe) {
+ int r;
+
+ if (!pipe->kern_crtc || !pipe->kern_connector || pipe->assigned)
+ return;
+
+ log_debug("card %s: restore pipe %u to %dx%d with connector %u",
+ pipe->card->name, pipe->id,
+ pipe->kern_crtc->mode.hdisplay, pipe->kern_crtc->mode.vdisplay,
+ pipe->kern_connector->id);
+
+ r = drmModeSetCrtc(pipe->card->fd,
+ pipe->kern_crtc->crtc_id,
+ pipe->kern_crtc->buffer_id,
+ pipe->kern_crtc->x,
+ pipe->kern_crtc->y,
+ &pipe->kern_connector->connector_id, 1,
+ &pipe->kern_crtc->mode);
+ if (r < 0)
+ log_error("card %s: RestoreCrtc(%d): %m",
+ pipe->card->name, (int)pipe->crtc_id);
+}
+
+static void gfx_pipe_print_wanted(sd_gfx_pipe *pipe, sd_gfx_mode *mode) {
+ unsigned int i;
+
+ if (!mode) {
+ log_debug("card %s: set pipe %u to <none>",
+ pipe->card->name, pipe->id);
+ return;
+ }
+
+ log_debug("card %s: set pipe %u to %dx%d with:",
+ pipe->card->name, pipe->id, (int)mode->info.hdisplay, (int)mode->info.vdisplay);
+
+ for (i = 0; i < pipe->want_connector_count; ++i)
+ log_debug(" connector: %u", pipe->want_connectors[i]->id);
+}
+
+static int gfx_pipe_commit_wanted(sd_gfx_pipe *pipe, sd_gfx_mode *mode) {
+ gfx_connector *connector, **tcons;
+ sd_gfx_mode *old_mode;
+ sd_gfx_fb *fb;
+ unsigned int i;
+ uint32_t *tids;
+ int r;
+
+ gfx_pipe_print_wanted(pipe, mode);
+
+ if (pipe->assigned)
+ return -EINVAL;
+
+ if (!mode) {
+ sd_gfx_mode_unref(pipe->mode);
+ pipe->mode = NULL;
+ sd_gfx_mode_unref(pipe->want_mode);
+ pipe->want_mode = NULL;
+ pipe->connector_count = 0;
+
+ r = drmModeSetCrtc(pipe->card->fd, pipe->crtc_id, 0, 0, 0, NULL, 0, NULL);
+ if (r < 0) {
+ if (errno == EACCES) {
+ gfx_card_async_sleep(pipe->card);
+ return -EACCES;
+ }
+
+ log_error("card %s: SetCrtc(%d): %m",
+ pipe->card->name, (int)pipe->crtc_id);
+ return -EINVAL;
+ }
+
+ pipe->assigned = 1;
+ pipe->page_flip = ++pipe->page_flip_cnt;
+
+ return 0;
+ }
+
+ if (!pipe->want_connector_count)
+ return -EINVAL;
+
+ for (i = 0; i < pipe->want_connector_count; ++i) {
+ connector = pipe->want_connectors[i];
+ if (connector->assigned)
+ return -EINVAL;
+
+ pipe->want_connector_ids[i] = connector->connector_id;
+ }
+
+ sd_gfx_mode_ref(mode);
+ sd_gfx_mode_unref(pipe->want_mode);
+ pipe->want_mode = mode;
+
+ r = gfx_plane_prepare_change(pipe->primary,
+ pipe->want_mode->info.hdisplay,
+ pipe->want_mode->info.vdisplay,
+ &fb);
+ if (r < 0)
+ return r;
+
+ r = drmModeSetCrtc(pipe->card->fd,
+ pipe->crtc_id,
+ fb->fb,
+ 0, 0,
+ pipe->want_connector_ids,
+ pipe->want_connector_count,
+ &pipe->want_mode->info);
+ if (r < 0) {
+ if (errno == EACCES) {
+ gfx_card_async_sleep(pipe->card);
+ r = -EACCES;
+ } else {
+ log_error("card %s: SetCrtc(%d): %m",
+ pipe->card->name, (int)pipe->crtc_id);
+ r = -EINVAL;
+ }
+
+ gfx_plane_cancel_change(pipe->primary, fb);
+ return r;
+ }
+
+ gfx_plane_finish_change(pipe->primary, fb);
+
+ for (i = 0; i < pipe->want_connector_count; ++i) {
+ connector = pipe->want_connectors[i];
+ connector->pipe = pipe;
+ connector->assigned = 1;
+ }
+
+ tcons = pipe->want_connectors;
+ pipe->want_connectors = pipe->connectors;
+ pipe->connectors = tcons;
+
+ tids = pipe->want_connector_ids;
+ pipe->want_connector_ids = pipe->connector_ids;
+ pipe->connector_ids = tids;
+
+ pipe->connector_count = pipe->want_connector_count;
+ pipe->want_connector_count = 0;
+
+ old_mode = pipe->mode;
+ pipe->mode = pipe->want_mode;
+ pipe->want_mode = NULL;
+
+ pipe->assigned = 1;
+ pipe->page_flip = ++pipe->page_flip_cnt;
+
+ gfx_pipe_call_wake_up(pipe, old_mode);
+ sd_gfx_mode_unref(old_mode);
+
+ return 0;
+}
+
+int sd_gfx_pipe_commit(sd_gfx_pipe *pipe) {
+ struct drm_mode_crtc_page_flip flip;
+ sd_gfx_fb *fb;
+ int r;
+
+ if (!pipe->card || !pipe->mode || !pipe->assigned)
+ return -ENODEV;
+ if (!pipe->card->real_awake)
+ return -EACCES;
+ if (pipe->page_flip != pipe->page_flip_cnt)
+ return -EALREADY;
+
+ if (pipe->primary->swap)
+ return -EALREADY;
+
+ fb = pipe->primary->swap_to;
+ if (!fb)
+ return 0;
+
+ memset(&flip, 0, sizeof(flip));
+ flip.fb_id = fb->fb;
+ flip.crtc_id = pipe->crtc_id;
+ flip.user_data = gfx_encode_arg(pipe->page_flip_cnt + 1, pipe->id);
+ flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
+
+ r = drmIoctl(pipe->card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip);
+ if (r < 0) {
+ if (errno == EACCES) {
+ gfx_card_async_sleep(pipe->card);
+ r = -EACCES;
+ } else {
+ log_error("card %s: PageFlip(): %m", pipe->card->name);
+ r = -EINVAL;
+ }
+
+ return r;
+ }
+
+ ++pipe->page_flip_cnt;
+ pipe->primary->swap = fb;
+ pipe->primary->swap_to = NULL;
+
+ return 0;
+}
+
+static void gfx_pipe_page_flip(sd_gfx_pipe *pipe, unsigned int frame, unsigned int sec, unsigned int usec, uint64_t page_flip) {
+ if (page_flip < pipe->page_flip_cnt)
+ return;
+
+ pipe->page_flip = pipe->page_flip_cnt;
+
+ gfx_plane_swap(pipe->primary);
+
+ if (!pipe->card || !pipe->mode || !pipe->assigned)
+ return;
+
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SWAP);
+}
+
+/*
+ * Modesetting
+ * The gfx_config helpers configure modesetting pipelines. They try to group
+ * connectors if clone-mode is requested, find suitable pipes and modes and
+ * perform modesetting.
+ *
+ * We allow to set some rules to control how modesetting is performed. But
+ * these are optional. The default rules should find suitable configurations
+ * for all systems.
+ */
+
+/* helper to collect connectors of a given pipe-config */
+static unsigned int gfx_config_collect_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe) {
+ gfx_connector *connector;
+ unsigned int num, i;
+
+ num = 0;
+ for (i = 0; i < card->connector_ids; ++i) {
+ connector = card->connectors[i];
+ if (connector && connector->want_config == conf_pipe && connector->connected && !connector->assigned)
+ card->tcons[num++] = connector;
+ }
+
+ return num;
+}
+
+/* helper to find a common pipe of a given pipe-config */
+static sd_gfx_pipe *gfx_config_find_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe, unsigned int num) {
+ sd_gfx_pipe *pipe;
+ unsigned int i;
+ uint32_t pipes;
+
+ pipes = 0xffffffff;
+ for (i = 0; i < num; ++i)
+ pipes &= card->tcons[i]->possible_pipes;
+
+ if (!pipes) {
+ log_warning("card %s: no common pipe found for pipe-config %u",
+ card->name, conf_pipe->config_id);
+ return NULL;
+ }
+
+ pipe = NULL;
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (pipe && (pipes & (1 << i)) && !pipe->assigned)
+ break;
+ }
+
+ if (i >= card->pipe_ids) {
+ log_warning("card %s: no unassigned pipe found for pipe-config %u",
+ card->name, conf_pipe->config_id);
+ return NULL;
+ }
+
+ return pipe;
+}
+
+/* helper to find a common mode of a given pipe-config */
+static sd_gfx_mode *gfx_config_find_mode(sd_gfx_card *card, gfx_config_pipe *conf_pipe, unsigned int num) {
+ gfx_connector *connector;
+ sd_gfx_mode *mode, *m;
+ unsigned int i, j;
+ bool auto_mode = false;
+
+ if (!strcmp(conf_pipe->mode, "auto"))
+ auto_mode = true;
+
+ mode = NULL;
+ for (i = 0; i < num; ++i) {
+ connector = card->tcons[i];
+
+ if (auto_mode) {
+ m = connector->preferred_mode;
+ if (!mode)
+ mode = m;
+ else if (m->info.hdisplay > mode->info.hdisplay)
+ mode = m;
+ else if (m->info.hdisplay == mode->info.hdisplay && m->info.vdisplay > mode->info.vdisplay)
+ mode = m;
+ } else {
+ for (j = 0; j < connector->mode_count; ++j) {
+ m = connector->modes[j];
+ if (!strcmp(m->info.name, conf_pipe->mode))
+ return m;
+ }
+ }
+ }
+
+ if (mode)
+ return mode;
+
+ if (auto_mode)
+ log_warning("card %s: no common mode found for pipe-config %u",
+ card->name, conf_pipe->config_id);
+ else
+ log_warning("card %s: mode '%s' not found for pipe-config %u",
+ card->name, conf_pipe->mode, conf_pipe->config_id);
+
+ return NULL;
+}
+
+static int gfx_config_apply_pipe(sd_gfx_card *card, gfx_config_pipe *conf_pipe) {
+ gfx_connector **tcons;
+ sd_gfx_pipe *pipe;
+ sd_gfx_mode *mode;
+ unsigned int num;
+ int r;
+
+ /* First step during setup. Takes a single pipe-configuration and
+ * tries to apply it. If it fails, we simply leave the entities
+ * unassigned so later steps pick them up.
+ * This step is a no-op if no pipe-configurations were supplied by
+ * the caller/user. */
+
+ if (!conf_pipe->mode || conf_pipe->disable)
+ return 0;
+
+ num = gfx_config_collect_pipe(card, conf_pipe);
+ if (!num)
+ return 0;
+
+ pipe = gfx_config_find_pipe(card, conf_pipe, num);
+ if (!pipe)
+ return 0;
+
+ mode = gfx_config_find_mode(card, conf_pipe, num);
+ if (!mode)
+ return 0;
+
+ pipe->want_rotation = conf_pipe->rotate;
+ pipe->want_connector_count = num;
+
+ tcons = pipe->want_connectors;
+ pipe->want_connectors = card->tcons;
+ card->tcons = tcons;
+
+ r = gfx_pipe_commit_wanted(pipe, mode);
+ if (r == -EACCES)
+ return r;
+
+ return 0;
+}
+
+static int gfx_config_apply_current(sd_gfx_card *card)
+{
+ sd_gfx_pipe *pipe;
+ gfx_connector *connector;
+ unsigned int i, j, num;
+ int r;
+
+ /* Optional second step during setup. Tries to take-over any existing
+ * setup that was left from the previous session but with our own FB.
+ * This is a conservative mode which allows us to avoid any pipe
+ * configurations on our own and rely on the previous session to do
+ * a suitable setup. We just take it over. */
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe || pipe->assigned || !pipe->kern_mode)
+ continue;
+
+ num = 0;
+ for (j = 0; j < card->connector_ids; ++j) {
+ connector = card->connectors[j];
+ if (!connector)
+ continue;
+ if (!connector->connected || connector->assigned)
+ continue;
+ if (!connector->encoder || connector->encoder->pipe != pipe)
+ continue;
+ if (connector->want_config)
+ continue;
+
+ pipe->want_connectors[num++] = connector;
+ }
+
+ if (!num)
+ continue;
+ if (num > 1 && !card->clone_tory)
+ continue;
+
+ pipe->want_rotation = 0;
+ pipe->want_connector_count = num;
+ r = gfx_pipe_commit_wanted(pipe, pipe->kern_mode);
+ if (r == -EACCES)
+ return r;
+ }
+
+ return 0;
+}
+
+static int gfx_config_apply_remaining(sd_gfx_card *card) {
+ sd_gfx_pipe *pipe;
+ gfx_connector *connector;
+ unsigned int i, j;
+ int r;
+
+ /* Last step when applying configurations. Picks up all unassigned
+ * pipes and tries to find an unassigned connector. Performs
+ * mode-setting if combination found, otherwise disables the pipe. */
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe || pipe->assigned)
+ continue;
+
+ for (j = 0; j < card->connector_ids; ++j) {
+ connector = card->connectors[j];
+ if (!connector || !connector->preferred_mode)
+ continue;
+ if (!connector->connected || connector->assigned)
+ continue;
+ if (!(connector->possible_pipes & (1 << i)))
+ continue;
+ if (connector->want_config && connector->want_config->disable)
+ continue;
+
+ pipe->want_rotation = connector->want_config ? connector->want_config->rotate : 0;
+ pipe->want_connector_count = 1;
+ pipe->want_connectors[0] = connector;
+
+ r = gfx_pipe_commit_wanted(pipe, connector->preferred_mode);
+ if (r >= 0)
+ break;
+ else if (r == -EACCES)
+ return r;
+ }
+
+ if (j >= card->connector_ids) {
+ pipe->want_connector_count = 0;
+ pipe->want_connectors[0] = NULL;
+
+ r = gfx_pipe_commit_wanted(pipe, NULL);
+ if (r == -EACCES)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int gfx_config_apply(sd_gfx_card *card) {
+ gfx_config_pipe *conf_pipe;
+ int r;
+
+ /* Called on WAKEUP. Tries to assign all unassigned pipes and
+ * connectors and performs mode-setting. You should call
+ * gfx_config_reset() on SLEEP so this will re-assign *all* entities,
+ * in case the setup changed while asleep. If *_reset() was not called,
+ * this will only touch unassigned entities. */
+
+ log_debug("card %s: apply pipe-configurations", card->name);
+ for (conf_pipe = card->configs; conf_pipe; ++conf_pipe) {
+ r = gfx_config_apply_pipe(card, conf_pipe);
+ if (r < 0)
+ return r;
+ }
+
+ if (card->tory) {
+ log_debug("card %s: adopt previous pipes", card->name);
+ r = gfx_config_apply_current(card);
+ if (r < 0)
+ return r;
+ }
+
+ log_debug("card %s: set remaining pipes", card->name);
+ return gfx_config_apply_remaining(card);
+}
+
+static int gfx_config_update(sd_gfx_card *card) {
+ sd_gfx_pipe *pipe;
+ gfx_connector *connector;
+ gfx_config_pipe *conf_pipe;
+ unsigned int i, j, num;
+ bool set;
+ int r;
+
+ /* Called on HOTPLUG events. All pipes and connectors were already
+ * assigned. We first verify that each pipe has all its connectors
+ * still set and re-assign them in case they aren't. We also pick up
+ * any possibly new connector that showed up and matches the config.
+ * Any unused pipe is reset to unassigned state again and we let
+ * gfx_config_apply_remaining() pick up any additional connector. */
+
+ for (i = 0; i < card->connector_ids; ++i) {
+ connector = card->connectors[i];
+ if (connector && connector->connected)
+ if (!connector->encoder || !connector->encoder->pipe)
+ connector->assigned = 0;
+ }
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe)
+ continue;
+
+ if (!pipe->assigned)
+ continue;
+
+ if (!pipe->mode) {
+ pipe->assigned = 0;
+ continue;
+ }
+
+ conf_pipe = NULL;
+ set = false;
+ num = 0;
+
+ /* pick up all previous connectors */
+ for (j = 0; j < pipe->connector_count; ++j) {
+ connector = pipe->connectors[j];
+ conf_pipe = connector->want_config;
+ if (!connector->connected) {
+ connector->assigned = 0;
+ continue;
+ }
+
+ pipe->want_connectors[num++] = connector;
+ if (!connector->encoder || connector->encoder->pipe != pipe)
+ set = true;
+ }
+
+ /* pick up any new connector */
+ if (conf_pipe) {
+ for (j = 0; j < card->connector_ids; ++j) {
+ connector = card->connectors[j];
+ if (!connector || !connector->connected)
+ continue;
+ if (connector->assigned || connector->want_config != conf_pipe)
+ continue;
+
+ pipe->want_connectors[num++] = connector;
+ set = true;
+ }
+ }
+
+ if (set || num != pipe->connector_count) {
+ pipe->assigned = 0;
+ for (j = 0; j < num; ++j)
+ pipe->want_connectors[j]->assigned = 0;
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+ if (num) {
+ pipe->want_connector_count = num;
+ r = gfx_pipe_commit_wanted(pipe, pipe->mode);
+ if (r == -EACCES)
+ return r;
+ }
+
+ if (!pipe->assigned) {
+ sd_gfx_mode_unref(pipe->mode);
+ pipe->mode = NULL;
+ sd_gfx_mode_unref(pipe->want_mode);
+ pipe->want_mode = NULL;
+ pipe->connector_count = 0;
+ pipe->want_connector_count = 0;
+ }
+ }
+ }
+
+ return gfx_config_apply_remaining(card);
+}
+
+static void gfx_config_assign(sd_gfx_card *card) {
+ gfx_config_pipe *conf_pipe;
+ gfx_connector *connector;
+ unsigned int i;
+ char **c;
+
+ /* Whenever card->configs changes, call this to re-assign pipe-configs
+ * to the correct connectors. This invalidates the config-caches so
+ * you *must* apply the config afterwards to take effect. */
+
+ for (i = 0; i < card->connector_ids; ++i)
+ if (card->connectors[i])
+ card->connectors[i]->want_config = NULL;
+
+ for (conf_pipe = card->configs; conf_pipe; ++conf_pipe) {
+ for (c = conf_pipe->connectors; c; ++c) {
+ for (i = 0; i < card->connector_ids; ++i) {
+ connector = card->connectors[i];
+ if (connector && gfx_connector_match(connector, *c))
+ break;
+ }
+
+ if (i >= card->connector_ids) {
+ log_warning("card %s: configured connector %s not found in pipe-config %u",
+ card->name, *c, conf_pipe->config_id);
+ continue;
+ } else if (connector->want_config) {
+ log_warning("card %s: configured connector %s already assigned (pipe-config %u:%u)",
+ card->name, *c, connector->want_config->config_id, conf_pipe->config_id);
+ continue;
+ }
+
+ connector->want_config = conf_pipe;
+ }
+ }
+}
+
+static void gfx_config_reset(sd_gfx_card *card) {
+ unsigned int i;
+ sd_gfx_pipe *pipe;
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe)
+ continue;
+
+ if (pipe->assigned && pipe->mode)
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_SLEEP);
+
+ pipe->assigned = 0;
+
+ sd_gfx_mode_unref(pipe->mode);
+ pipe->mode = NULL;
+ sd_gfx_mode_unref(pipe->want_mode);
+ pipe->want_mode = NULL;
+ pipe->want_rotation = 0;
+ pipe->connector_count = 0;
+ pipe->want_connector_count = 0;
+ }
+
+ for (i = 0; i < card->connector_ids; ++i) {
+ if (card->connectors[i])
+ card->connectors[i]->assigned = 0;
+ }
+}
+
+/*
+ * Card Manager
+ * Each DRM device is represented by a sd_gfx_card object. It manages all DRM
+ * resources, allocates them during device setup and monitors the devices for
+ * hotplug events.
+ *
+ * A newly allocated card is always asleep. If you want to use it, you need to
+ * wake it up. On each wake-up, it tries to restore the previous modeset state
+ * and notifies you about new or gone pipes.
+ * A device can be put asleep asynchronously. The card manager notices that
+ * and puts the device temporarily asleep. You must never assume the device is
+ * awake and fully functional as access might be revoked at any time.
+ */
+
+static void gfx_card_call(sd_gfx_card *card, unsigned int type) {
+ sd_gfx_card_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = type;
+
+ if (card->event_fn)
+ card->event_fn(card, card->fn_data, &ev);
+}
+
+static void gfx_card_page_flip(sd_gfx_card *card, unsigned int frame, unsigned int sec, unsigned int usec, uint64_t data) {
+ unsigned int id;
+ sd_gfx_pipe *pipe;
+
+ gfx_decode_arg(&data, &id);
+ if (id >= card->pipe_ids)
+ return;
+
+ pipe = card->pipes[id];
+ if (!pipe)
+ return;
+
+ gfx_pipe_page_flip(pipe, frame, sec, usec, data);
+}
+
+static int gfx_card_io_fn(sd_event_source *source, int fd, uint32_t revents, void *data) {
+ sd_gfx_card *card = data;
+ struct drm_event *ev;
+ struct drm_event_vblank *vb;
+ char buf[1024];
+ ssize_t i, l;
+
+ if (revents & (EPOLLHUP | EPOLLERR)) {
+ log_debug("card %s: hangup", card->name);
+ sd_event_source_set_enabled(source, SD_EVENT_OFF);
+ gfx_card_call(card, SD_GFX_CARD_HUP);
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN))
+ return 0;
+
+ l = read(fd, buf, sizeof(buf));
+ if (l < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ log_debug("card %s: read(): %m", card->name);
+ return 0;
+ } else if (!l) {
+ log_debug("card %s: read() EOF", card->name);
+ return 0;
+ } else if (l < (ssize_t)sizeof(*ev)) {
+ log_error("card %s: read(): %zu < %zu",
+ card->name, (size_t)l, sizeof(*ev));
+ return 0;
+ }
+
+ i = 0;
+ while (i < l) {
+ ev = (void*)&buf[i];
+
+ switch (ev->type) {
+ case DRM_EVENT_FLIP_COMPLETE:
+ vb = (void*)ev;
+ gfx_card_page_flip(card, vb->sequence, vb->tv_sec, vb->tv_usec, vb->user_data);
+ break;
+ default:
+ break;
+ }
+
+ i += ev->length;
+ }
+
+ return 0;
+}
+
+static void gfx_card_print_dev(sd_gfx_card *card) {
+ unsigned int i, j;
+ sd_gfx_pipe *pipe;
+ gfx_encoder *encoder;
+ gfx_connector *connector;
+ sd_gfx_mode *mode;
+
+ log_debug("card %s: current device-state is:", card->name);
+
+ log_debug(" pipes:");
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe) {
+ log_debug(" %u missing", i);
+ continue;
+ }
+
+ log_debug(" (id: %u crtc: %d)",
+ i, (int)pipe->crtc_id);
+ if (pipe->kern_mode)
+ log_debug(" mode: %dx%d",
+ (int)pipe->kern_mode->info.hdisplay,
+ (int)pipe->kern_mode->info.vdisplay);
+ else
+ log_debug(" mode: <none>");
+ }
+
+ log_debug(" encoders:");
+ for (i = 0; i < card->encoder_ids; ++i) {
+ encoder = card->encoders[i];
+ if (!encoder) {
+ log_debug(" %u missing", i);
+ continue;
+ }
+
+ log_debug(" (id: %u encoder: %d)",
+ i, (int)encoder->encoder_id);
+ if (encoder->pipe)
+ log_debug(" pipe: %u", encoder->pipe->id);
+ else
+ log_debug(" pipe: <none>");
+ log_debug(" can: pipes: %08x clones: %08x",
+ (unsigned int)encoder->possible_pipes,
+ (unsigned int)encoder->possible_clones);
+ }
+
+ log_debug(" connectors:");
+ for (i = 0; i < card->connector_ids; ++i) {
+ connector = card->connectors[i];
+ if (!connector) {
+ log_debug(" %u missing", i);
+ continue;
+ }
+
+ log_debug(" (id: %u connector: %d state: %d dpms: %u)",
+ i, (int)connector->connector_id, (int)connector->connected, connector->dpms);
+ log_debug(" type: %s-%d", connector->type, (int)connector->type_id);
+ if (connector->encoder)
+ log_debug(" encoder: %u", connector->encoder->id);
+ else
+ log_debug(" encoder: <none>");
+ log_debug(" can: encoders: %08x pipes: %08x",
+ (unsigned int)connector->possible_encoders,
+ (unsigned int)connector->possible_pipes);
+
+ for (j = 0; j < connector->mode_count; ++j) {
+ mode = connector->modes[j];
+ log_debug(" mode: %dx%d%s%s",
+ (int)mode->info.hdisplay, (int)mode->info.vdisplay,
+ (mode == connector->preferred_mode) ? " +preferred" : "",
+ (mode == connector->default_mode) ? " +default" : "");
+ }
+ }
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(drmModeRes*, drmModeFreeResources);
+
+static int gfx_card_open(sd_gfx_card *card) {
+ _cleanup_(drmModeFreeResourcesp) drmModeRes *res = NULL;
+ drmModeCrtc *crtc;
+ drmModeEncoder *enc;
+ drmModeConnector *con;
+ int i, r;
+ sd_gfx_pipe *pipe;
+ gfx_encoder *encoder;
+ gfx_connector *connector;
+
+ /* All Mode-Objects of a DRM device are known during probe-time.
+ * Hotplugging of single mode-objects is not supported by the DRM
+ * API. Thus, the API relies on the object-order to assign IDs
+ * implicitly. This means, the 3rd encoder in res->encoders gets
+ * the implicit ID "3" (together with the explicit mode-object ID
+ * "encoder_id"). The implicit IDs are per-type, the explicit IDs
+ * are per-device.
+ * Implicit IDs are used for bitmaps ("possible_crtcs",
+ * "possible_clones", ...), explicit IDs are used for object
+ * identification in global scope (ioctls, modesetting, ...).
+ * Note that implicit IDs are not really part of the kernel ABI,
+ * but still there's no way to use the API without them. Kind of
+ * ugly, but everyone relies on these IDs so that makes them part
+ * of the ABI, right? */
+
+ res = drmModeGetResources(card->fd);
+ if (!res) {
+ log_error("card %s: GetResources(): %m", card->name);
+ return -EINVAL;
+ }
+
+ card->pipes = calloc(res->count_crtcs, sizeof(*card->pipes));
+ if (!card->pipes)
+ return log_oom();
+
+ card->encoders = calloc(res->count_encoders, sizeof(*card->encoders));
+ if (!card->encoders)
+ return log_oom();
+
+ card->connectors = calloc(res->count_connectors, sizeof(*card->connectors));
+ if (!card->connectors)
+ return log_oom();
+
+ card->tcons = calloc(res->count_connectors, sizeof(*card->tcons));
+ if (!card->tcons)
+ return log_oom();
+
+ /* We *must* be careful here to assign the correct implicit IDs
+ * to each mode-object. If we cannot create an object, its ID
+ * must stay allocated (but unassigned)! Otherwise, our IDs are
+ * not in sync with the kernel. */
+
+ for (i = 0; i < res->count_crtcs; ++i) {
+ if (i >= 32) {
+ log_warning("card %s: too many pipes", card->name);
+ break;
+ }
+
+ crtc = drmModeGetCrtc(card->fd, res->crtcs[i]);
+ if (!crtc) {
+ log_error("card %s: GetCrtc(%d): %m",
+ card->name, (int)res->crtcs[i]);
+ } else {
+ pipe = NULL;
+ r = gfx_pipe_new(&pipe, card, crtc, res->count_connectors);
+ if (r >= 0)
+ card->pipes[pipe->id] = pipe;
+
+ drmModeFreeCrtc(crtc);
+ }
+ ++card->pipe_ids;
+ }
+
+ for (i = 0; i < res->count_encoders; ++i) {
+ if (i >= 32) {
+ log_warning("card %s: too many encoders", card->name);
+ break;
+ }
+
+ enc = drmModeGetEncoder(card->fd, res->encoders[i]);
+ if (!enc) {
+ log_error("card %s: GetEncoder(%d): %m",
+ card->name, (int)res->encoders[i]);
+ } else {
+ encoder = NULL;
+ r = gfx_encoder_new(&encoder, card, enc);
+ if (r >= 0)
+ card->encoders[encoder->id] = encoder;
+
+ drmModeFreeEncoder(enc);
+ }
+ ++card->encoder_ids;
+ }
+
+ for (i = 0; i < res->count_connectors; ++i) {
+ if (i >= 32) {
+ log_warning("card %s: too many connectors", card->name);
+ break;
+ }
+
+ con = drmModeGetConnector(card->fd, res->connectors[i]);
+ if (!con) {
+ log_error("card %s: GetConnector(%d): %m",
+ card->name, (int)res->connectors[i]);
+ } else {
+ connector = NULL;
+ r = gfx_connector_new(&connector, card, con);
+ if (r >= 0)
+ card->connectors[connector->id] = connector;
+
+ drmModeFreeConnector(con);
+ }
+ ++card->connector_ids;
+ }
+
+ return 0;
+}
+
+int sd_gfx_card_new(sd_gfx_card **out,
+ const char *name,
+ int fd,
+ sd_event *event) {
+ sd_gfx_card *card;
+ uint64_t has_dumb = 0;
+ int r;
+
+ assert(name);
+ assert(fd >= 0);
+ assert(event);
+
+ log_debug("card %s: new", name);
+
+ r = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
+ if (r < 0 || !has_dumb) {
+ log_error("card %s: dumb buffers not supported", name);
+ return -ENODEV;
+ }
+
+ card = calloc(1, sizeof(*card));
+ if (!card)
+ return log_oom();
+
+ card->name = strdup(name);
+ if (!card->name) {
+ r = log_oom();
+ goto err_free;
+ }
+
+ card->fd = fd;
+ card->tory = 1;
+
+ r = sd_event_add_io(event, fd, EPOLLIN, gfx_card_io_fn, card, &card->fd_source);
+ if (r < 0)
+ goto err_free;
+
+ card->event = event;
+ sd_event_ref(card->event);
+
+ r = gfx_card_open(card);
+ if (r < 0) {
+ sd_gfx_card_free(card);
+ return r;
+ }
+
+ gfx_config_assign(card);
+ *out = card;
+ return 0;
+
+err_free:
+ free(card->name);
+ free(card);
+ return r;
+}
+
+void sd_gfx_card_free(sd_gfx_card *card) {
+ unsigned int i;
+ sd_gfx_pipe *pipe;
+
+ if (!card)
+ return;
+
+ log_debug("card %s: free", card->name);
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe)
+ continue;
+
+ gfx_pipe_unlink(pipe);
+ sd_gfx_pipe_unref(pipe);
+ }
+
+ for (i = 0; i < card->encoder_ids; ++i)
+ gfx_encoder_free(card->encoders[i]);
+
+ for (i = 0; i < card->connector_ids; ++i)
+ gfx_connector_free(card->connectors[i]);
+
+ free(card->tcons);
+ free(card->connectors);
+ free(card->encoders);
+ free(card->pipes);
+ sd_event_source_set_enabled(card->fd_source, SD_EVENT_OFF);
+ sd_event_source_unref(card->fd_source);
+ sd_event_unref(card->event);
+ close_nointr(card->fd);
+ free(card->name);
+ free(card);
+}
+
+void sd_gfx_card_set_data(sd_gfx_card *card, void *data) {
+ card->data = data;
+}
+
+void *sd_gfx_card_get_data(sd_gfx_card *card) {
+ return card->data;
+}
+
+void sd_gfx_card_set_fn_data(sd_gfx_card *card, void *fn_data) {
+ card->fn_data = fn_data;
+}
+
+void *sd_gfx_card_get_fn_data(sd_gfx_card *card) {
+ return card->fn_data;
+}
+
+void sd_gfx_card_set_event_fn(sd_gfx_card *card, sd_gfx_card_event_fn event_fn) {
+ card->event_fn = event_fn;
+}
+
+static void gfx_card_refresh(sd_gfx_card *card) {
+ unsigned int i;
+ sd_gfx_pipe *pipe;
+ gfx_encoder *encoder;
+ gfx_connector *connector;
+
+ log_debug("card %s: refresh device state", card->name);
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe)
+ continue;
+
+ if (!card->public)
+ gfx_pipe_call(pipe, SD_GFX_CARD_PIPE_CREATE);
+
+ gfx_pipe_refresh(pipe, NULL);
+ }
+
+ card->public = 1;
+
+ for (i = 0; i < card->encoder_ids; ++i) {
+ encoder = card->encoders[i];
+ if (!encoder)
+ continue;
+
+ gfx_encoder_refresh(encoder, NULL);
+ }
+
+ for (i = 0; i < card->connector_ids; ++i) {
+ connector = card->connectors[i];
+ if (!connector)
+ continue;
+
+ gfx_connector_refresh(connector, NULL);
+ }
+
+ gfx_card_print_dev(card);
+}
+
+int sd_gfx_card_wake_up(sd_gfx_card *card) {
+ if (card->awake)
+ return 0;
+
+ log_debug("card %s: wake up", card->name);
+
+ card->awake = 1;
+ card->real_awake = 1;
+ gfx_card_refresh(card);
+
+ return gfx_config_apply(card);
+}
+
+void sd_gfx_card_sleep(sd_gfx_card *card) {
+ if (!card->awake)
+ return;
+
+ log_debug("card %s: sync-sleep", card->name);
+
+ card->awake = 0;
+ card->real_awake = 0;
+ gfx_config_reset(card);
+}
+
+bool sd_gfx_card_is_awake(sd_gfx_card *card) {
+ return card->real_awake;
+}
+
+static void gfx_card_async_sleep(sd_gfx_card *card) {
+ if (card->real_awake) {
+ card->real_awake = 0;
+ log_debug("card %s: async-sleep", card->name);
+ }
+}
+
+void sd_gfx_card_hotplug(sd_gfx_card *card) {
+ if (!card->real_awake)
+ return;
+
+ gfx_card_refresh(card);
+ gfx_config_update(card);
+}
+
+void sd_gfx_card_restore(sd_gfx_card *card) {
+ unsigned int i;
+ sd_gfx_pipe *pipe;
+
+ if (card->awake)
+ sd_gfx_card_sleep(card);
+
+ for (i = 0; i < card->pipe_ids; ++i) {
+ pipe = card->pipes[i];
+ if (!pipe)
+ continue;
+
+ gfx_pipe_commit_kern(pipe);
+ }
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index 96d034a..44237a1 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -37,6 +37,13 @@ typedef struct sd_gfx_buffer sd_gfx_buffer;

typedef struct sd_gfx_font sd_gfx_font;

+typedef struct sd_gfx_fb sd_gfx_fb;
+typedef struct sd_gfx_plane sd_gfx_plane;
+typedef struct sd_gfx_mode sd_gfx_mode;
+typedef struct sd_gfx_pipe sd_gfx_pipe;
+typedef struct sd_gfx_card_event sd_gfx_card_event;
+typedef struct sd_gfx_card sd_gfx_card;
+
typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
typedef struct sd_gfx_kbd sd_gfx_kbd;

@@ -67,6 +74,194 @@ unsigned int sd_gfx_font_get_height(sd_gfx_font *font);

void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);

+/* framebuffer */
+
+typedef void (*sd_gfx_fb_unlink_fn) (sd_gfx_fb *fb, void *fn_data);
+typedef void (*sd_gfx_fb_unpin_fn) (sd_gfx_fb *fb, void *fn_data);
+
+int sd_gfx_fb_new(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t handles[4],
+ uint32_t width,
+ uint32_t height,
+ uint32_t strides[4],
+ uint32_t offsets[4],
+ uint64_t size,
+ uint64_t map_offset);
+int sd_gfx_fb_new_rgb(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t handle,
+ uint32_t width,
+ uint32_t height,
+ uint32_t stride,
+ uint32_t offset,
+ uint64_t size,
+ uint64_t map_offset);
+int sd_gfx_fb_new_dumb(sd_gfx_fb **out,
+ sd_gfx_plane *plane,
+ uint32_t format,
+ uint32_t bpp,
+ uint32_t width,
+ uint32_t height);
+
+void sd_gfx_fb_ref(sd_gfx_fb *fb);
+void sd_gfx_fb_unref(sd_gfx_fb *fb);
+
+void sd_gfx_fb_set_data(sd_gfx_fb *fb, void *data);
+void *sd_gfx_fb_get_data(sd_gfx_fb *fb);
+void sd_gfx_fb_set_fn_data(sd_gfx_fb *fb, void *fn_data);
+void *sd_gfx_fb_get_fn_data(sd_gfx_fb *fb);
+
+void sd_gfx_fb_set_fns(sd_gfx_fb *fb, sd_gfx_fb_unlink_fn unlink_fn, sd_gfx_fb_unpin_fn unpin_fn);
+
+sd_gfx_plane *sd_gfx_fb_get_plane(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_format(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_width(sd_gfx_fb *fb);
+uint32_t sd_gfx_fb_get_height(sd_gfx_fb *fb);
+
+void sd_gfx_fb_fill(sd_gfx_fb *fb,
+ uint32_t color,
+ unsigned int pos_x,
+ unsigned int pos_y,
+ unsigned int width,
+ unsigned int height);
+void sd_gfx_fb_blend_bichrome(sd_gfx_fb *fb,
+ uint32_t fg_color,
+ uint32_t bg_color,
+ unsigned int pos_x,
+ unsigned int pos_y,
+ const sd_gfx_buffer *source);
+
+/* plane */
+
+typedef int (*sd_gfx_plane_prepare_fn) (sd_gfx_plane *plane,
+ void *fn_data,
+ unsigned int width,
+ unsigned int height,
+ sd_gfx_fb **fb);
+typedef void (*sd_gfx_plane_cancel_fn) (sd_gfx_plane *plane,
+ void *fn_data,
+ sd_gfx_fb *fb);
+typedef void (*sd_gfx_plane_finish_fn) (sd_gfx_plane *plane,
+ void *fn_data,
+ sd_gfx_fb *fb);
+
+void sd_gfx_plane_ref(sd_gfx_plane *plane);
+void sd_gfx_plane_unref(sd_gfx_plane *plane);
+
+void sd_gfx_plane_set_data(sd_gfx_plane *plane, void *data);
+void *sd_gfx_plane_get_data(sd_gfx_plane *plane);
+void sd_gfx_plane_set_fn_data(sd_gfx_plane *plane, void *fn_data);
+void *sd_gfx_plane_get_fn_data(sd_gfx_plane *plane);
+
+void sd_gfx_plane_set_fns(sd_gfx_plane *plane,
+ sd_gfx_plane_prepare_fn prepare_fn,
+ sd_gfx_plane_cancel_fn cancel_fn,
+ sd_gfx_plane_finish_fn finish_fn);
+
+unsigned int sd_gfx_plane_get_id(sd_gfx_plane *plane);
+sd_gfx_card *sd_gfx_plane_get_card(sd_gfx_plane *plane);
+sd_gfx_pipe *sd_gfx_plane_get_pipe(sd_gfx_plane *plane);
+
+bool sd_gfx_plane_is_enabled(sd_gfx_plane *plane);
+void sd_gfx_plane_enable(sd_gfx_plane *plane);
+void sd_gfx_plane_disable(sd_gfx_plane *plane);
+
+bool sd_gfx_plane_supports_format(sd_gfx_plane *plane, uint32_t format);
+
+bool sd_gfx_plane_supports_pipe(sd_gfx_plane *plane, sd_gfx_pipe *pipe);
+
+sd_gfx_fb *sd_gfx_plane_get_front(sd_gfx_plane *plane);
+sd_gfx_fb *sd_gfx_plane_get_back(sd_gfx_plane *plane);
+void sd_gfx_plane_swap_to(sd_gfx_plane *plane, sd_gfx_fb *fb);
+
+/* mode */
+
+void sd_gfx_mode_ref(sd_gfx_mode *mode);
+void sd_gfx_mode_unref(sd_gfx_mode *mode);
+
+unsigned int sd_gfx_mode_get_width(sd_gfx_mode *mode);
+unsigned int sd_gfx_mode_get_height(sd_gfx_mode *mode);
+const char *sd_gfx_mode_get_name(sd_gfx_mode *mode);
+
+/* pipe */
+
+enum {
+ SD_GFX_DPMS_ON,
+ SD_GFX_DPMS_STANDBY,
+ SD_GFX_DPMS_SUSPEND,
+ SD_GFX_DPMS_OFF,
+ SD_GFX_DPMS_UNKNOWN,
+};
+
+void sd_gfx_pipe_ref(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_unref(sd_gfx_pipe *pipe);
+
+void sd_gfx_pipe_set_data(sd_gfx_pipe *pipe, void *data);
+void *sd_gfx_pipe_get_data(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_set_fn_data(sd_gfx_pipe *pipe, void *fn_data);
+void *sd_gfx_pipe_get_fn_data(sd_gfx_pipe *pipe);
+
+unsigned int sd_gfx_pipe_get_id(sd_gfx_pipe *pipe);
+
+unsigned int sd_gfx_pipe_get_dpms(sd_gfx_pipe *pipe);
+void sd_gfx_pipe_set_dpms(sd_gfx_pipe *pipe, unsigned int dpms);
+
+sd_gfx_mode *sd_gfx_pipe_get_mode(sd_gfx_pipe *pipe);
+sd_gfx_plane *sd_gfx_pipe_get_cursor(sd_gfx_pipe *pipe);
+sd_gfx_plane *sd_gfx_pipe_get_primary_plane(sd_gfx_pipe *pipe);
+
+int sd_gfx_pipe_attach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane);
+void sd_gfx_pipe_detach_plane(sd_gfx_pipe *pipe, sd_gfx_plane *plane);
+
+int sd_gfx_pipe_commit(sd_gfx_pipe *pipe);
+
+/* card */
+
+enum {
+ SD_GFX_CARD_HUP,
+
+ SD_GFX_CARD_PIPE_CREATE,
+ SD_GFX_CARD_PIPE_DESTROY,
+ SD_GFX_CARD_PIPE_WAKE_UP,
+ SD_GFX_CARD_PIPE_SLEEP,
+ SD_GFX_CARD_PIPE_SWAP,
+
+ SD_GFX_CARD_PLANE_CREATE,
+ SD_GFX_CARD_PLANE_DESTROY,
+};
+
+struct sd_gfx_card_event {
+ unsigned int type;
+
+ sd_gfx_pipe *pipe;
+ sd_gfx_plane *plane;
+ sd_gfx_mode *old_mode;
+ sd_gfx_fb *fb;
+};
+
+typedef void (*sd_gfx_card_event_fn) (sd_gfx_card *card, void *fn_data, sd_gfx_card_event *event);
+
+int sd_gfx_card_new(sd_gfx_card **out,
+ const char *name,
+ int fd,
+ sd_event *event);
+void sd_gfx_card_free(sd_gfx_card *card);
+
+void sd_gfx_card_set_data(sd_gfx_card *card, void *data);
+void *sd_gfx_card_get_data(sd_gfx_card *card);
+void sd_gfx_card_set_fn_data(sd_gfx_card *card, void *fn_data);
+void *sd_gfx_card_get_fn_data(sd_gfx_card *card);
+void sd_gfx_card_set_event_fn(sd_gfx_card *card, sd_gfx_card_event_fn event_fn);
+
+int sd_gfx_card_wake_up(sd_gfx_card *card);
+void sd_gfx_card_sleep(sd_gfx_card *card);
+bool sd_gfx_card_is_awake(sd_gfx_card *card);
+void sd_gfx_card_hotplug(sd_gfx_card *card);
+void sd_gfx_card_restore(sd_gfx_card *card);
+
/* keyboard */

enum {
--
1.8.4.2
Lennart Poettering
2013-11-27 22:24:29 UTC
Permalink
Post by David Herrmann
+typedef struct gfx_config_pipe gfx_config_pipe;
+typedef struct gfx_connector gfx_connector;
+typedef struct gfx_encoder gfx_encoder;
+
+/* clock-wise framebuffer rotation */
+enum {
+ GFX_ROTATE_0,
+ GFX_ROTATE_90,
+ GFX_ROTATE_180,
+ GFX_ROTATE_270,
+};
+
+struct gfx_config_pipe {
+ unsigned int config_id;
+ char **connectors;
+ char *mode;
+ unsigned int rotate;
+
+ unsigned int disable : 1;
C99 bool plz! (here and everywhere else...)
Post by David Herrmann
+ memset(handles, 0, sizeof(handles));
+ memset(strides, 0, sizeof(strides));
+ memset(offsets, 0, sizeof(offsets));
zero(handles) is so much nicer... (here and everywhere...)
Post by David Herrmann
+static int gfx_pipe_new(sd_gfx_pipe **out, sd_gfx_card *card, drmModeCrtc *crtc, unsigned int connector_count) {
+ sd_gfx_pipe *pipe;
+ int r;
+
+ pipe = calloc(1, sizeof(*pipe));
+ if (!pipe)
+ return log_oom();
+
+ pipe->ref = 1;
+ pipe->card = card;
+ pipe->id = card->pipe_ids;
+ pipe->crtc_id = crtc->crtc_id;
+ pipe->page_flip_cnt = 1;
+
+ pipe->connectors = calloc(connector_count, sizeof(*pipe->connectors));
+ if (!pipe->connectors) {
+ r = log_oom();
+ goto err_pipe;
+ }
+
+ pipe->want_connectors = calloc(connector_count, sizeof(*pipe->want_connectors));
+ if (!pipe->want_connectors) {
+ r = log_oom();
+ goto err_connectors;
+ }
+
+ pipe->connector_ids = calloc(connector_count, sizeof(*pipe->connector_ids));
+ if (!pipe->connector_ids) {
+ r = log_oom();
+ goto err_want_connectors;
+ }
+
+ pipe->want_connector_ids = calloc(connector_count, sizeof(*pipe->want_connector_ids));
+ if (!pipe->want_connector_ids) {
+ r = log_oom();
+ goto err_connector_ids;
+ }
+
+ r = gfx_plane_new_primary(&pipe->primary, pipe);
+ if (r < 0)
+ goto err_want_connector_ids;
+
+ gfx_pipe_refresh(pipe, crtc);
+
+ *out = pipe;
+ return 0;
+
+ free(pipe->want_connector_ids);
+ free(pipe->connector_ids);
+ free(pipe->want_connectors);
+ free(pipe->connectors);
+ free(pipe);
+ return r;
+}
+
For clean-up code like this I usually find it nicer to simply have a
destructor function that is robust to destruct half-initialized objects
and then just invoke this here.
Post by David Herrmann
+/* framebuffer */
+
+typedef void (*sd_gfx_fb_unlink_fn) (sd_gfx_fb *fb, void *fn_data);
+typedef void (*sd_gfx_fb_unpin_fn) (sd_gfx_fb *fb, void *fn_data);
For the types that actually feel like primitive types (in contrast to
objects), we usually appended a libc style _t to our names.

I can't really say much about the actual drm stuff going on here, just
my usualy whining about logging from lib code, C99 bools, zero()...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:39:13 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:24 PM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
+typedef struct gfx_config_pipe gfx_config_pipe;
+typedef struct gfx_connector gfx_connector;
+typedef struct gfx_encoder gfx_encoder;
+
+/* clock-wise framebuffer rotation */
+enum {
+ GFX_ROTATE_0,
+ GFX_ROTATE_90,
+ GFX_ROTATE_180,
+ GFX_ROTATE_270,
+};
+
+struct gfx_config_pipe {
+ unsigned int config_id;
+ char **connectors;
+ char *mode;
+ unsigned int rotate;
+
+ unsigned int disable : 1;
C99 bool plz! (here and everywhere else...)
Post by David Herrmann
+ memset(handles, 0, sizeof(handles));
+ memset(strides, 0, sizeof(strides));
+ memset(offsets, 0, sizeof(offsets));
zero(handles) is so much nicer... (here and everywhere...)
Post by David Herrmann
+static int gfx_pipe_new(sd_gfx_pipe **out, sd_gfx_card *card, drmModeCrtc *crtc, unsigned int connector_count) {
+ sd_gfx_pipe *pipe;
+ int r;
+
+ pipe = calloc(1, sizeof(*pipe));
+ if (!pipe)
+ return log_oom();
+
+ pipe->ref = 1;
+ pipe->card = card;
+ pipe->id = card->pipe_ids;
+ pipe->crtc_id = crtc->crtc_id;
+ pipe->page_flip_cnt = 1;
+
+ pipe->connectors = calloc(connector_count, sizeof(*pipe->connectors));
+ if (!pipe->connectors) {
+ r = log_oom();
+ goto err_pipe;
+ }
+
+ pipe->want_connectors = calloc(connector_count, sizeof(*pipe->want_connectors));
+ if (!pipe->want_connectors) {
+ r = log_oom();
+ goto err_connectors;
+ }
+
+ pipe->connector_ids = calloc(connector_count, sizeof(*pipe->connector_ids));
+ if (!pipe->connector_ids) {
+ r = log_oom();
+ goto err_want_connectors;
+ }
+
+ pipe->want_connector_ids = calloc(connector_count, sizeof(*pipe->want_connector_ids));
+ if (!pipe->want_connector_ids) {
+ r = log_oom();
+ goto err_connector_ids;
+ }
+
+ r = gfx_plane_new_primary(&pipe->primary, pipe);
+ if (r < 0)
+ goto err_want_connector_ids;
+
+ gfx_pipe_refresh(pipe, crtc);
+
+ *out = pipe;
+ return 0;
+
+ free(pipe->want_connector_ids);
+ free(pipe->connector_ids);
+ free(pipe->want_connectors);
+ free(pipe->connectors);
+ free(pipe);
+ return r;
+}
+
For clean-up code like this I usually find it nicer to simply have a
destructor function that is robust to destruct half-initialized objects
and then just invoke this here.
Ouh, indeed. I use calloc() anyway, so I could just make this one
single goto. Or make the destructor robust and call it instead, yepp.
Fixed.
Post by Lennart Poettering
Post by David Herrmann
+/* framebuffer */
+
+typedef void (*sd_gfx_fb_unlink_fn) (sd_gfx_fb *fb, void *fn_data);
+typedef void (*sd_gfx_fb_unpin_fn) (sd_gfx_fb *fb, void *fn_data);
For the types that actually feel like primitive types (in contrast to
objects), we usually appended a libc style _t to our names.
Ouh, libudev uses "udev_log_fn" so I followed that style. I thought
that's what we use for callback-prototypes. Is that just a left-over
from pre-systemd times? I can change it all to _t, if it is.

Thanks
David
Post by Lennart Poettering
I can't really say much about the actual drm stuff going on here, just
my usualy whining about logging from lib code, C99 bools, zero()...
Lennart
--
Lennart Poettering, Red Hat
Lennart Poettering
2013-12-11 00:47:09 UTC
Permalink
Post by David Herrmann
Post by Lennart Poettering
For the types that actually feel like primitive types (in contrast to
objects), we usually appended a libc style _t to our names.
Ouh, libudev uses "udev_log_fn" so I followed that style. I thought
that's what we use for callback-prototypes. Is that just a left-over
from pre-systemd times? I can change it all to _t, if it is.
udev does a couple of things differently i guess, since it predates the
rest of systemd. I mean, it is called "libudev", and not
"libsystemd-udev" after all...

Kay keeps talking of replacing libudev by libsystemd-udev and doing a
bit of API clean-ups on the way, but I am not too convinced yet...

Lennart
--
Lennart Poettering, Red Hat
Kay Sievers
2013-12-11 01:05:04 UTC
Permalink
On Wed, Dec 11, 2013 at 1:47 AM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
Post by Lennart Poettering
For the types that actually feel like primitive types (in contrast to
objects), we usually appended a libc style _t to our names.
Ouh, libudev uses "udev_log_fn" so I followed that style. I thought
that's what we use for callback-prototypes. Is that just a left-over
from pre-systemd times? I can change it all to _t, if it is.
udev does a couple of things differently i guess, since it predates the
rest of systemd. I mean, it is called "libudev", and not
"libsystemd-udev" after all...
Kay keeps talking of replacing libudev by libsystemd-udev and doing a
bit of API clean-ups on the way, but I am not too convinced yet...
Yeah, talk is cheap. And if we do that, it would be libsystemd-device,
and be fully based on kdbus and not netlink. But all that is for a
time when we all are really bored. There is more important stuff to do
at the moment. :)

Kay
Greg KH
2013-11-27 21:32:49 UTC
Permalink
Post by David Herrmann
Hi
So booting without VTs is still a horrible experience on desktop-linux. If
anyone tried, they were presumably running away in tears. This series is a new
attempt to fix that.
The first 4 patches are just small fixes/helpers. Patch 5 to 8 introduce sd-gfx
and patch 9-11 add tests/examples for sd-gfx. The final patch adds a first user
of sd-gfx to replace the kernel-internal terminal-emulator. The commit-msgs
should explain each change at length so I won't repeat it here.
To anyone interested in testing: If you apply this series, you can run
"./test-kbd" to test keyboard handling, "./test-gfx" to test graphics rendering
and "./systemd-consoled" to run the terminal-emulator. Note that you must run
all these from *within* an existing logind-session. That usually means from a
TEXT-VT. Also note that all these programs currently ignore the underlying VT
entirely (in case you run with CONFIG_VT=y). This has the side-effect, that all
keystrokes go to both, the VT and the application. This will obviously be
changed in later versions, but I wanted to avoid any VT calls in these helpers.
I currently work on logind to fix that.
I had to strip the font-patches due to ML size-constraints. The full series is
http://cgit.freedesktop.org/~dvdhrm/systemd/log/?h=console
Notes on where this is going, and *why*, below..
Very nice work, keep it up.

greg k-h
Lennart Poettering
2013-11-27 22:44:16 UTC
Permalink
On Wed, 27.11.13 19:48, David Herrmann (***@gmail.com) wrote:

Looks pretty good. Commented on the invidiaul patches, but mostly looks
good.

I am not sure about the whole approach of making this a potentially
public library though... Especially the monitor stuff sounds so
specific. Commiting to a stable API for that given that there are very
few other projects who could ever make use of this sounds dangerous at
best. Especially since the api exposes bit values for enums and things,
which makes me especially afraid...

Lennart
--
Lennart Poettering, Red Hat
David Herrmann
2013-11-28 08:08:59 UTC
Permalink
Hi

On Wed, Nov 27, 2013 at 11:44 PM, Lennart Poettering
Post by Lennart Poettering
Looks pretty good. Commented on the invidiaul patches, but mostly looks
good.
I am not sure about the whole approach of making this a potentially
public library though... Especially the monitor stuff sounds so
specific. Commiting to a stable API for that given that there are very
few other projects who could ever make use of this sounds dangerous at
best. Especially since the api exposes bit values for enums and things,
which makes me especially afraid...
I have no short-term plan to export sd-gfx. It's just like the 3rd
time I happen to write a DRM abstraction and I really don't wanna do
that again. Hence, I thought putting it into sd-gfx would reduce the
burden to export it eventually. Furthermore, as there'll be like 5
daemons using it, it would be nice to have a clean API even if it's
only systemd-internal (it forces one to write better code, imho). Last
but not least, it makes debugging easier if there's a clean cut
between sd-gfx and each application.

But: the current API will never get exported as it is. Obviously, the
monitor (and probably font) is better kept internal. But I see no
reason to add sd-gfx-internal.h / sd-gfx-internal.la now. In case we
ever export it, we would have to decide which parts to split off
(which is a rather mechanical tasks, so no reason to account for it
right now).

So lets just say I hate camel-case object-names so I went for
libsystemd-gfx to have an excuse to use
lower-case-separated-by-underscore names, right? Lets treat sd-gfx as
internal library for now.

A few comments beforehand:
- regarding logging in libraries: I *want* verbose errors/warnings in
sd-gfx. Especially if modesetting, keymap-compilation or bus-requests
fail, I want some rather verbose messages in the log. Considering that
if a single page-flip fails, I will not abort but continue the modeset
(you don't want your console to abort due to minor timing issues in
the DRM driver, right?), so the application might not even get an
-EXYZ error value. I tried adding a ->log_fn() like libudev, but then
again, sd-gfx is internal so I skipped that and just called log_meta()
directly. Is that fine for now? If not, I can change all these to
log_debug() and make the applications more verbose.
- new0(), zero(), ..., yepp, kay already told me but I forgot.. I will
fix them up.
- moving "out" parameters to the end... will do so.

Thanks for the review. Few more comments on the individual patches.
David
Lennart Poettering
2013-12-11 00:35:34 UTC
Permalink
Post by David Herrmann
- regarding logging in libraries: I *want* verbose errors/warnings in
sd-gfx. Especially if modesetting, keymap-compilation or bus-requests
fail, I want some rather verbose messages in the log. Considering that
if a single page-flip fails, I will not abort but continue the modeset
(you don't want your console to abort due to minor timing issues in
the DRM driver, right?), so the application might not even get an
-EXYZ error value. I tried adding a ->log_fn() like libudev, but then
That libudev does that is mostly because it predates log.c, it wouldn't
do that either anymore if we'd write it now...
Post by David Herrmann
again, sd-gfx is internal so I skipped that and just called log_meta()
directly. Is that fine for now? If not, I can change all these to
log_debug() and make the applications more verbose.
- new0(), zero(), ..., yepp, kay already told me but I forgot.. I will
fix them up.
- moving "out" parameters to the end... will do so.
Hmm, interesting problem. I have the suspicion though that this either
shouldn't be "library" code, or that they should be downgraded to debug
level messages.

Lennart
--
Lennart Poettering, Red Hat
Kay Sievers
2013-12-11 00:50:34 UTC
Permalink
On Wed, Dec 11, 2013 at 1:35 AM, Lennart Poettering
Post by Lennart Poettering
Post by David Herrmann
- regarding logging in libraries: I *want* verbose errors/warnings in
sd-gfx. Especially if modesetting, keymap-compilation or bus-requests
fail, I want some rather verbose messages in the log. Considering that
if a single page-flip fails, I will not abort but continue the modeset
(you don't want your console to abort due to minor timing issues in
the DRM driver, right?), so the application might not even get an
-EXYZ error value. I tried adding a ->log_fn() like libudev, but then
That libudev does that is mostly because it predates log.c, it wouldn't
do that either anymore if we'd write it now...
Hmm, for complex subsystems, error objects probably make the most
sense, like we have for dbus.

For things that really just want to log things from libraries, I still
think that allowing to hook up the user of the library to its own
logging facility makes some sense; instead of logging not enough, or
just spitting things out on stderr.

Not sure what a good advise is here for library, we should sort that
out and also update libabc with that, if it's significantly different
from what udev does.
Post by Lennart Poettering
Post by David Herrmann
again, sd-gfx is internal so I skipped that and just called log_meta()
directly. Is that fine for now?
Please do not call log_meta() for debugging, unless you wrap it in
your own macro to avoid composition of potentially expensive
parameters to this function. Debug messages are usually thrown away,
but only *inside* the function and all parameters need to be prepared
to just get thrown away. For that reason, log_debug() wraps a check in
it.

Kay
Loading...