lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <75efda63-6656-4dfb-a308-cb5d6e08a8d5@suse.de>
Date: Thu, 5 Dec 2024 17:21:52 +0100
From: Thomas Zimmermann <tzimmermann@...e.de>
To: Jocelyn Falempe <jfalempe@...hat.com>,
 Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
 Maxime Ripard <mripard@...nel.org>, David Airlie <airlied@...il.com>,
 Daniel Vetter <daniel@...ll.ch>, John Ogness <john.ogness@...utronix.de>,
 Javier Martinez Canillas <javierm@...hat.com>,
 "Guilherme G . Piccoli" <gpiccoli@...lia.com>,
 bluescreen_avenger@...izon.net, Caleb Connolly <caleb.connolly@...aro.org>,
 Petr Mladek <pmladek@...e.com>, Jani Nikula <jani.nikula@...ux.intel.com>,
 dri-devel@...ts.freedesktop.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v9 2/6] drm/log: Introduce a new boot logger to draw the
 kmsg on the screen




Am 04.12.24 um 16:45 schrieb Jocelyn Falempe:
> drm_log is a simple logger that uses the drm_client API to print the
> kmsg boot log on the screen. This is not a full replacement to fbcon,
> as it will only print the kmsg. It will never handle user input, or a
> terminal because this is better done in userspace.
>
> Design decisions:
>   * It uses the drm_client API, so it should work on all drm drivers
>     from the start.
>   * It doesn't scroll the message, that way it doesn't need to redraw
>     the whole screen for each new message.
>     It also means it doesn't have to keep drawn messages in memory, to
>     redraw them when scrolling.
>   * It uses the new non-blocking console API, so it should work well
>     with PREEMPT_RT.
>
> This patch also adds a Kconfig menu to select the drm client to use.
> It can be overwritten on the kernel command line with:
> drm_client_lib.active=log or drm_client_lib.active=fbdev
>
> Signed-off-by: Jocelyn Falempe <jfalempe@...hat.com>
> Reviewed-by: John Ogness <john.ogness@...utronix.de> # console API

Reviewed-by: Thomas Zimmermann <tzimmermann@...e.de>

> ---
>
> v2:
>   * Use vmap_local() api, with that change, I've tested it successfully on simpledrm, virtio-gpu, amdgpu, and nouveau.
>   * Stop drawing when the drm_master is taken. This avoid wasting CPU cycle if the buffer is not visible.
>   * Use deferred probe. Only do the probe the first time there is a log to draw. With this, if you boot with quiet, drm_log won't do any modeset.
>   * Add color support for the timestamp prefix, like what dmesg does.
>   * Add build dependency on  disabling the fbdev emulation, as they are both drm_client, and there is no way to choose which one gets the focus.
>
> v3:
>   * Remove the work thread and circular buffer, and use the new write_thread() console API.
>   * Register a console for each drm driver.
>
> v4:
>   * Can be built as a module, even if that's not really useful.
>   * Rebased on top of "drm: Introduce DRM client library" series from Thomas Zimmermann.
>   * Add a Kconfig menu to choose between drm client.
>   
> v5:
>   * Build drm_log in drm_client_lib module, to avoid circular dependency.
>
> v8:
>   * Rebased after drm client moved to drivers/gpu/drm/clients/
>   * Rename DRM_LOG to DRM_CLIENT_LOG (Thomas Zimmermann)
>   * Add an info message if no clients are initialized in drm_client_setup()
>   
> v9:
>   * Add cflags to remove the "../" when including drm internal headers (Jani Nikula)
>   * Order select alphabetically in KConfig (Thomas Zimmermann)
>   * Replace drm_info with drm_dbg, to be less verbose (Thomas Zimmermann)
>   * Rename module parameter to drm_client_lib.active (Thomas Zimmermann)
>   * Warn if drm_client_lib.active is malformated (Thomas Zimmermann)
>   
>   drivers/gpu/drm/clients/Kconfig               |  48 +++
>   drivers/gpu/drm/clients/Makefile              |   3 +
>   drivers/gpu/drm/clients/drm_client_internal.h |   6 +
>   drivers/gpu/drm/clients/drm_client_setup.c    |  29 +-
>   drivers/gpu/drm/clients/drm_log.c             | 370 ++++++++++++++++++
>   5 files changed, 452 insertions(+), 4 deletions(-)
>   create mode 100644 drivers/gpu/drm/clients/drm_log.c
>
> diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
> index 01ad3b000130..c18decc90200 100644
> --- a/drivers/gpu/drm/clients/Kconfig
> +++ b/drivers/gpu/drm/clients/Kconfig
> @@ -12,6 +12,7 @@ config DRM_CLIENT_LIB
>   config DRM_CLIENT_SELECTION
>   	tristate
>   	depends on DRM
> +	select DRM_CLIENT_LIB if DRM_CLIENT_LOG
>   	select DRM_CLIENT_LIB if DRM_FBDEV_EMULATION
>   	help
>   	  Drivers that support in-kernel DRM clients have to select this
> @@ -70,4 +71,51 @@ config DRM_FBDEV_LEAK_PHYS_SMEM
>   	  If in doubt, say "N" or spread the word to your closed source
>   	  library vendor.
>   
> +config DRM_CLIENT_LOG
> +	bool "Print the kernel boot message on the screen"
> +	depends on DRM_CLIENT_SELECTION
> +	select DRM_CLIENT
> +	select DRM_CLIENT_SETUP
> +	select DRM_DRAW
> +	help
> +	  This enable a drm logger, that will print the kernel messages to the
> +	  screen until the userspace is ready to take over.
> +
> +	  If you only need logs, but no terminal, or if you prefer userspace
> +	  terminal, say "Y".
> +
> +choice
> +	prompt "Default DRM Client"
> +	depends on DRM_CLIENT_SELECTION
> +	default DRM_CLIENT_DEFAULT_FBDEV
> +	help
> +	  Selects the default drm client.
> +
> +	  The selection made here can be overridden by using the kernel
> +	  command line 'drm_client_lib.active=fbdev' option.
> +
> +config DRM_CLIENT_DEFAULT_FBDEV
> +	bool "fbdev"
> +	depends on DRM_FBDEV_EMULATION
> +	help
> +	  Use fbdev emulation as default drm client. This is needed to have
> +	  fbcon on top of a drm driver.
> +
> +config DRM_CLIENT_DEFAULT_LOG
> +	bool "log"
> +	depends on DRM_CLIENT_LOG
> +	help
> +	  Use drm log as default drm client. This will display boot logs on the
> +	  screen, but doesn't implement a full terminal. For that you will need
> +	  a userspace terminal using drm/kms.
> +
> +endchoice
> +
> +config DRM_CLIENT_DEFAULT
> +       string
> +       depends on DRM_CLIENT
> +       default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
> +       default "log" if DRM_CLIENT_DEFAULT_LOG
> +       default ""
> +
>   endmenu
> diff --git a/drivers/gpu/drm/clients/Makefile b/drivers/gpu/drm/clients/Makefile
> index 1d004ec92e1e..c16addbc327f 100644
> --- a/drivers/gpu/drm/clients/Makefile
> +++ b/drivers/gpu/drm/clients/Makefile
> @@ -1,5 +1,8 @@
>   # SPDX-License-Identifier: GPL-2.0
>   
> +subdir-ccflags-y += -I$(src)/..
> +
>   drm_client_lib-y := drm_client_setup.o
> +drm_client_lib-$(CONFIG_DRM_CLIENT_LOG) += drm_log.o
>   drm_client_lib-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_client.o
>   obj-$(CONFIG_DRM_CLIENT_LIB) += drm_client_lib.o
> diff --git a/drivers/gpu/drm/clients/drm_client_internal.h b/drivers/gpu/drm/clients/drm_client_internal.h
> index 23258934956a..6dc078bf6503 100644
> --- a/drivers/gpu/drm/clients/drm_client_internal.h
> +++ b/drivers/gpu/drm/clients/drm_client_internal.h
> @@ -16,4 +16,10 @@ static inline int drm_fbdev_client_setup(struct drm_device *dev,
>   }
>   #endif
>   
> +#ifdef CONFIG_DRM_CLIENT_LOG
> +void drm_log_register(struct drm_device *dev);
> +#else
> +static inline void drm_log_register(struct drm_device *dev) {}
> +#endif
> +
>   #endif
> diff --git a/drivers/gpu/drm/clients/drm_client_setup.c b/drivers/gpu/drm/clients/drm_client_setup.c
> index 4b211a4812b5..e17265039ca8 100644
> --- a/drivers/gpu/drm/clients/drm_client_setup.c
> +++ b/drivers/gpu/drm/clients/drm_client_setup.c
> @@ -7,6 +7,12 @@
>   
>   #include "drm_client_internal.h"
>   
> +static char drm_client_default[16] = CONFIG_DRM_CLIENT_DEFAULT;
> +module_param_string(active, drm_client_default, sizeof(drm_client_default), 0444);
> +MODULE_PARM_DESC(active,
> +		 "Choose which drm client to start, default is"
> +		 CONFIG_DRM_CLIENT_DEFAULT "]");
> +
>   /**
>    * drm_client_setup() - Setup in-kernel DRM clients
>    * @dev: DRM device
> @@ -25,11 +31,26 @@
>    */
>   void drm_client_setup(struct drm_device *dev, const struct drm_format_info *format)
>   {
> -	int ret;
>   
> -	ret = drm_fbdev_client_setup(dev, format);
> -	if (ret)
> -		drm_warn(dev, "Failed to set up DRM client; error %d\n", ret);
> +#ifdef CONFIG_DRM_FBDEV_EMULATION
> +	if (!strcmp(drm_client_default, "fbdev")) {
> +		int ret;
> +
> +		ret = drm_fbdev_client_setup(dev, format);
> +		if (ret)
> +			drm_warn(dev, "Failed to set up DRM client; error %d\n", ret);
> +		return;
> +	}
> +#endif
> +
> +#ifdef CONFIG_DRM_CLIENT_LOG
> +	if (!strcmp(drm_client_default, "log")) {
> +		drm_log_register(dev);
> +		return;
> +	}
> +#endif
> +	if (strcmp(drm_client_default, ""))
> +		drm_warn(dev, "Unknown DRM client %s\n", drm_client_default);
>   }
>   EXPORT_SYMBOL(drm_client_setup);
>   
> diff --git a/drivers/gpu/drm/clients/drm_log.c b/drivers/gpu/drm/clients/drm_log.c
> new file mode 100644
> index 000000000000..4e07bff6c864
> --- /dev/null
> +++ b/drivers/gpu/drm/clients/drm_log.c
> @@ -0,0 +1,370 @@
> +// SPDX-License-Identifier: GPL-2.0 or MIT
> +/*
> + * Copyright (c) 2024 Red Hat.
> + * Author: Jocelyn Falempe <jfalempe@...hat.com>
> + */
> +
> +#include <linux/console.h>
> +#include <linux/font.h>
> +#include <linux/init.h>
> +#include <linux/iosys-map.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +
> +#include <drm/drm_client.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_print.h>
> +
> +#include "drm_client_internal.h"
> +#include "drm_draw_internal.h"
> +
> +MODULE_AUTHOR("Jocelyn Falempe");
> +MODULE_DESCRIPTION("DRM boot logger");
> +MODULE_LICENSE("GPL");
> +
> +/**
> + * DOC: overview
> + *
> + * This is a simple graphic logger, to print the kernel message on screen, until
> + * a userspace application is able to take over.
> + * It is only for debugging purpose.
> + */
> +
> +struct drm_log_scanout {
> +	struct drm_client_buffer *buffer;
> +	const struct font_desc *font;
> +	u32 rows;
> +	u32 columns;
> +	u32 line;
> +	u32 format;
> +	u32 px_width;
> +	u32 front_color;
> +};
> +
> +struct drm_log {
> +	struct mutex lock;
> +	struct drm_client_dev client;
> +	struct console con;
> +	bool probed;
> +	u32 n_scanout;
> +	struct drm_log_scanout *scanout;
> +};
> +
> +static struct drm_log *client_to_drm_log(struct drm_client_dev *client)
> +{
> +	return container_of(client, struct drm_log, client);
> +}
> +
> +static struct drm_log *console_to_drm_log(struct console *con)
> +{
> +	return container_of(con, struct drm_log, con);
> +}
> +
> +static void drm_log_blit(struct iosys_map *dst, unsigned int dst_pitch,
> +			 const u8 *src, unsigned int src_pitch,
> +			 u32 height, u32 width, u32 scale, u32 px_width, u32 color)
> +{
> +	switch (px_width) {
> +	case 2:
> +		drm_draw_blit16(dst, dst_pitch, src, src_pitch, height, width, scale, color);
> +		break;
> +	case 3:
> +		drm_draw_blit24(dst, dst_pitch, src, src_pitch, height, width, scale, color);
> +		break;
> +	case 4:
> +		drm_draw_blit32(dst, dst_pitch, src, src_pitch, height, width, scale, color);
> +		break;
> +	default:
> +		WARN_ONCE(1, "Can't blit with pixel width %d\n", px_width);
> +	}
> +}
> +
> +static void drm_log_clear_line(struct drm_log_scanout *scanout, u32 line)
> +{
> +	struct drm_framebuffer *fb = scanout->buffer->fb;
> +	unsigned long height = scanout->font->height;
> +	struct iosys_map map;
> +	struct drm_rect r = DRM_RECT_INIT(0, line * height, fb->width, height);
> +
> +	if (drm_client_buffer_vmap_local(scanout->buffer, &map))
> +		return;
> +	iosys_map_memset(&map, r.y1 * fb->pitches[0], 0, height * fb->pitches[0]);
> +	drm_client_buffer_vunmap_local(scanout->buffer);
> +	drm_client_framebuffer_flush(scanout->buffer, &r);
> +}
> +
> +static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s,
> +			      unsigned int len)
> +{
> +	struct drm_framebuffer *fb = scanout->buffer->fb;
> +	struct iosys_map map;
> +	const struct font_desc *font = scanout->font;
> +	size_t font_pitch = DIV_ROUND_UP(font->width, 8);
> +	const u8 *src;
> +	u32 px_width = fb->format->cpp[0];
> +	struct drm_rect r = DRM_RECT_INIT(0, scanout->line * font->height,
> +					  fb->width, (scanout->line + 1) * font->height);
> +	u32 i;
> +
> +	if (drm_client_buffer_vmap_local(scanout->buffer, &map))
> +		return;
> +
> +	iosys_map_incr(&map, r.y1 * fb->pitches[0]);
> +	for (i = 0; i < len && i < scanout->columns; i++) {
> +		src = drm_draw_get_char_bitmap(font, s[i], font_pitch);
> +		drm_log_blit(&map, fb->pitches[0], src, font_pitch, font->height, font->width,
> +			     1, px_width, scanout->front_color);
> +		iosys_map_incr(&map, font->width * px_width);
> +	}
> +
> +	scanout->line++;
> +	if (scanout->line >= scanout->rows)
> +		scanout->line = 0;
> +	drm_client_buffer_vunmap_local(scanout->buffer);
> +	drm_client_framebuffer_flush(scanout->buffer, &r);
> +}
> +
> +static void drm_log_draw_new_line(struct drm_log_scanout *scanout,
> +				  const char *s, unsigned int len)
> +{
> +	if (scanout->line == 0) {
> +		drm_log_clear_line(scanout, 0);
> +		drm_log_clear_line(scanout, 1);
> +		drm_log_clear_line(scanout, 2);
> +	} else if (scanout->line + 2 < scanout->rows)
> +		drm_log_clear_line(scanout, scanout->line + 2);
> +
> +	drm_log_draw_line(scanout, s, len);
> +}
> +
> +static void drm_log_draw_kmsg_record(struct drm_log_scanout *scanout,
> +				     const char *s, unsigned int len)
> +{
> +	/* do not print the ending \n character */
> +	if (s[len - 1] == '\n')
> +		len--;
> +
> +	while (len > scanout->columns) {
> +		drm_log_draw_new_line(scanout, s, scanout->columns);
> +		s += scanout->columns;
> +		len -= scanout->columns;
> +	}
> +	if (len)
> +		drm_log_draw_new_line(scanout, s, len);
> +}
> +
> +static u32 drm_log_find_usable_format(struct drm_plane *plane)
> +{
> +	int i;
> +
> +	for (i = 0; i < plane->format_count; i++)
> +		if (drm_draw_color_from_xrgb8888(0xffffff, plane->format_types[i]) != 0)
> +			return plane->format_types[i];
> +	return DRM_FORMAT_INVALID;
> +}
> +
> +static int drm_log_setup_modeset(struct drm_client_dev *client,
> +				 struct drm_mode_set *mode_set,
> +				 struct drm_log_scanout *scanout)
> +{
> +	struct drm_crtc *crtc = mode_set->crtc;
> +	u32 width = mode_set->mode->hdisplay;
> +	u32 height = mode_set->mode->vdisplay;
> +	u32 format;
> +
> +	scanout->font = get_default_font(width, height, NULL, NULL);
> +	if (!scanout->font)
> +		return -ENOENT;
> +
> +	format = drm_log_find_usable_format(crtc->primary);
> +	if (format == DRM_FORMAT_INVALID)
> +		return -EINVAL;
> +
> +	scanout->buffer = drm_client_framebuffer_create(client, width, height, format);
> +	if (IS_ERR(scanout->buffer)) {
> +		drm_warn(client->dev, "drm_log can't create framebuffer %d %d %p4cc\n",
> +			 width, height, &format);
> +		return -ENOMEM;
> +	}
> +	mode_set->fb = scanout->buffer->fb;
> +	scanout->rows = height / scanout->font->height;
> +	scanout->columns = width / scanout->font->width;
> +	scanout->front_color = drm_draw_color_from_xrgb8888(0xffffff, format);
> +	return 0;
> +}
> +
> +static int drm_log_count_modeset(struct drm_client_dev *client)
> +{
> +	struct drm_mode_set *mode_set;
> +	int count = 0;
> +
> +	mutex_lock(&client->modeset_mutex);
> +	drm_client_for_each_modeset(mode_set, client)
> +		count++;
> +	mutex_unlock(&client->modeset_mutex);
> +	return count;
> +}
> +
> +static void drm_log_init_client(struct drm_log *dlog)
> +{
> +	struct drm_client_dev *client = &dlog->client;
> +	struct drm_mode_set *mode_set;
> +	int i, max_modeset;
> +	int n_modeset = 0;
> +
> +	dlog->probed = true;
> +
> +	if (drm_client_modeset_probe(client, 0, 0))
> +		return;
> +
> +	max_modeset = drm_log_count_modeset(client);
> +	if (!max_modeset)
> +		return;
> +
> +	dlog->scanout = kcalloc(max_modeset, sizeof(*dlog->scanout), GFP_KERNEL);
> +	if (!dlog->scanout)
> +		return;
> +
> +	mutex_lock(&client->modeset_mutex);
> +	drm_client_for_each_modeset(mode_set, client) {
> +		if (!mode_set->mode)
> +			continue;
> +		if (drm_log_setup_modeset(client, mode_set, &dlog->scanout[n_modeset]))
> +			continue;
> +		n_modeset++;
> +	}
> +	mutex_unlock(&client->modeset_mutex);
> +	if (n_modeset == 0)
> +		goto err_nomodeset;
> +
> +	if (drm_client_modeset_commit(client))
> +		goto err_failed_commit;
> +
> +	dlog->n_scanout = n_modeset;
> +	return;
> +
> +err_failed_commit:
> +	for (i = 0; i < n_modeset; i++)
> +		drm_client_framebuffer_delete(dlog->scanout[i].buffer);
> +
> +err_nomodeset:
> +	kfree(dlog->scanout);
> +	dlog->scanout = NULL;
> +}
> +
> +static void drm_log_free_scanout(struct drm_client_dev *client)
> +{
> +	struct drm_log *dlog = client_to_drm_log(client);
> +	int i;
> +
> +	if (dlog->n_scanout) {
> +		for (i = 0; i < dlog->n_scanout; i++)
> +			drm_client_framebuffer_delete(dlog->scanout[i].buffer);
> +		dlog->n_scanout = 0;
> +		kfree(dlog->scanout);
> +		dlog->scanout = NULL;
> +	}
> +}
> +
> +static void drm_log_client_unregister(struct drm_client_dev *client)
> +{
> +	struct drm_log *dlog = client_to_drm_log(client);
> +	struct drm_device *dev = client->dev;
> +
> +	unregister_console(&dlog->con);
> +
> +	mutex_lock(&dlog->lock);
> +	drm_log_free_scanout(client);
> +	drm_client_release(client);
> +	mutex_unlock(&dlog->lock);
> +	kfree(dlog);
> +	drm_dbg(dev, "Unregistered with drm log\n");
> +}
> +
> +static int drm_log_client_hotplug(struct drm_client_dev *client)
> +{
> +	struct drm_log *dlog = client_to_drm_log(client);
> +
> +	mutex_lock(&dlog->lock);
> +	drm_log_free_scanout(client);
> +	dlog->probed = false;
> +	mutex_unlock(&dlog->lock);
> +	return 0;
> +}
> +
> +static const struct drm_client_funcs drm_log_client_funcs = {
> +	.owner		= THIS_MODULE,
> +	.unregister	= drm_log_client_unregister,
> +	.hotplug	= drm_log_client_hotplug,
> +};
> +
> +static void drm_log_write_thread(struct console *con, struct nbcon_write_context *wctxt)
> +{
> +	struct drm_log *dlog = console_to_drm_log(con);
> +	int i;
> +
> +	if (!dlog->probed)
> +		drm_log_init_client(dlog);
> +
> +	for (i = 0; i < dlog->n_scanout; i++)
> +		drm_log_draw_kmsg_record(&dlog->scanout[i], wctxt->outbuf, wctxt->len);
> +}
> +
> +static void drm_log_lock(struct console *con, unsigned long *flags)
> +{
> +	struct drm_log *dlog = console_to_drm_log(con);
> +
> +	mutex_lock(&dlog->lock);
> +	migrate_disable();
> +}
> +
> +static void drm_log_unlock(struct console *con, unsigned long flags)
> +{
> +	struct drm_log *dlog = console_to_drm_log(con);
> +
> +	migrate_enable();
> +	mutex_unlock(&dlog->lock);
> +}
> +
> +static void drm_log_register_console(struct console *con)
> +{
> +	strscpy(con->name, "drm_log");
> +	con->write_thread = drm_log_write_thread;
> +	con->device_lock = drm_log_lock;
> +	con->device_unlock = drm_log_unlock;
> +	con->flags = CON_PRINTBUFFER | CON_NBCON;
> +	con->index = -1;
> +
> +	register_console(con);
> +}
> +
> +/**
> + * drm_log_register() - Register a drm device to drm_log
> + * @dev: the drm device to register.
> + */
> +void drm_log_register(struct drm_device *dev)
> +{
> +	struct drm_log *new;
> +
> +	new = kzalloc(sizeof(*new), GFP_KERNEL);
> +	if (!new)
> +		goto err_warn;
> +
> +	mutex_init(&new->lock);
> +	if (drm_client_init(dev, &new->client, "drm_log", &drm_log_client_funcs))
> +		goto err_free;
> +
> +	drm_client_register(&new->client);
> +
> +	drm_log_register_console(&new->con);
> +
> +	drm_dbg(dev, "Registered with drm log as %s\n", new->con.name);
> +	return;
> +
> +err_free:
> +	kfree(new);
> +err_warn:
> +	drm_warn(dev, "Failed to register with drm log\n");
> +}

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstrasse 146, 90461 Nuernberg, Germany
GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman
HRB 36809 (AG Nuernberg)


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ