lkml.org 
[lkml]   [2017]   [Sep]   [1]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
From
Date
SubjectRe: [patch v7 1/4] drivers: jtag: Add JTAG core driver
Nit inline ;-)

On Fri, Sep 1, 2017 at 9:06 AM, Oleksandr Shamray
<oleksandrs@mellanox.com> wrote:
> Initial patch for JTAG friver

s/friver/driver
> JTAG class driver provide infrastructure to support hardware/software
> JTAG platform drivers. It provide user layer API interface for flashing
> and debugging external devices which equipped with JTAG interface
> using standard transactions.
>
> Driver exposes set of IOCTL to user space for:
> - XFER:
> - SIR (Scan Instruction Register, IEEE 1149.1 Data Register scan);
> - SDR (Scan Data Register, IEEE 1149.1 Instruction Register scan);
> - RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified
> number of clocks).
> - SIOCFREQ/GIOCFREQ for setting and reading JTAG frequency.
>
> Driver core provides set of internal APIs for allocation and
> registration:
> - jtag_register;
> - jtag_unregister;
> - jtag_alloc;
> - jtag_free;
>
> Platform driver on registration with jtag-core creates the next
> entry in dev folder:
> /dev/jtagX
>
> Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com>
> Signed-off-by: Jiri Pirko <jiri@mellanox.com>
> ---
> v6->v7
> Notifications from kbuild test robot <lkp@intel.com>
> - Remove include asm/types.h from jtag.h
> - Add include <linux/types.h> to jtag.c
>
> v5->v6
> v4->v5
>
> v3->v4
> Comments pointed by Arnd Bergmann <arnd@arndb.de>
> - change transaction pointer tdio type to __u64
> - change internal status type from enum to __u32
> - reorder jtag_xfer members to aviod the implied padding
> - add __packed attribute to jtag_xfer and jtag_run_test_idle
>
> v2->v3
> Notifications from kbuild test robot <lkp@intel.com>
> - Change include path to <linux/types.h> in jtag.h
>
> v1->v2
> Comments pointed by Greg KH <gregkh@linuxfoundation.org>
> - Change license type from GPLv2/BSD to GPLv2
> - Change type of variables which crossed user/kernel to __type
> - Remove "default n" from Kconfig
>
> Comments pointed by Andrew Lunn <andrew@lunn.ch>
> - Change list_add_tail in jtag_unregister to list_del
>
> Comments pointed by Neil Armstrong <narmstrong@baylibre.com>
> - Add SPDX-License-Identifier instead of license text
>
> Comments pointed by Arnd Bergmann <arnd@arndb.de>
> - Change __copy_to_user to memdup_user
> - Change __put_user to put_user
> - Change type of variables to __type for compatible 32 and 64-bit systems
> - Add check for maximum xfer data size
> - Change lookup data mechanism to get jtag data from inode
> - Add .compat_ioctl to file ops
> - Add mem alignment for jtag priv data
>
> Comments pointed by Tobias Klauser <tklauser@distanz.ch>
> - Change function names to avoid match with variable types
> - Fix description for jtag_ru_test_idle in uapi jtag.h
> - Fix misprints IDEL/IDLE, trough/through
> ---
> Documentation/ioctl/ioctl-number.txt | 2 +
> MAINTAINERS | 8 +
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/jtag/Kconfig | 16 ++
> drivers/jtag/Makefile | 1 +
> drivers/jtag/jtag.c | 312 ++++++++++++++++++++++++++++++++++
> include/linux/jtag.h | 48 +++++
> include/uapi/linux/jtag.h | 111 ++++++++++++
> 9 files changed, 501 insertions(+), 0 deletions(-)
> create mode 100644 drivers/jtag/Kconfig
> create mode 100644 drivers/jtag/Makefile
> create mode 100644 drivers/jtag/jtag.c
> create mode 100644 include/linux/jtag.h
> create mode 100644 include/uapi/linux/jtag.h
>
> diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
> index 3e3fdae..1af2508 100644
> --- a/Documentation/ioctl/ioctl-number.txt
> +++ b/Documentation/ioctl/ioctl-number.txt
> @@ -321,6 +321,8 @@ Code Seq#(hex) Include File Comments
> 0xB0 all RATIO devices in development:
> <mailto:vgo@ratio.de>
> 0xB1 00-1F PPPoX <mailto:mostrows@styx.uwaterloo.ca>
> +0xB2 00-0f linux/jtag.h JTAG driver
> + <mailto:oleksandrs@mellanox.com>
> 0xB3 00 linux/mmc/ioctl.h
> 0xB4 00-0F linux/gpio.h <mailto:linux-gpio@vger.kernel.org>
> 0xB5 00-0F uapi/linux/rpmsg.h <mailto:linux-remoteproc@vger.kernel.org>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 205d397..141aeaf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7292,6 +7292,14 @@ L: linux-serial@vger.kernel.org
> S: Maintained
> F: drivers/tty/serial/jsm/
>
> +JTAG SUBSYSTEM
> +M: Oleksandr Shamray <oleksandrs@mellanox.com>
> +M: Vadim Pasternak <vadimp@mellanox.com>
> +S: Maintained
> +F: include/linux/jtag.h
> +F: include/uapi/linux/jtag.h
> +F: drivers/jtag/
> +
> K10TEMP HARDWARE MONITORING DRIVER
> M: Clemens Ladisch <clemens@ladisch.de>
> L: linux-hwmon@vger.kernel.org
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index 505c676..2214678 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -208,4 +208,6 @@ source "drivers/tee/Kconfig"
>
> source "drivers/mux/Kconfig"
>
> +source "drivers/jtag/Kconfig"
> +
> endmenu
> diff --git a/drivers/Makefile b/drivers/Makefile
> index dfdcda0..6a2059b 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -182,3 +182,4 @@ obj-$(CONFIG_FPGA) += fpga/
> obj-$(CONFIG_FSI) += fsi/
> obj-$(CONFIG_TEE) += tee/
> obj-$(CONFIG_MULTIPLEXER) += mux/
> +obj-$(CONFIG_JTAG) += jtag/
> diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig
> new file mode 100644
> index 0000000..0fad1a3
> --- /dev/null
> +++ b/drivers/jtag/Kconfig
> @@ -0,0 +1,16 @@
> +menuconfig JTAG
> + tristate "JTAG support"
> + ---help---
> + This provides basic core functionality support for jtag class devices
> + Hardware equipped with JTAG microcontroller which can be built
> + on top of this drivers. Driver exposes the set of IOCTL to the
> + user space for:
> + SIR (Scan Instruction Register, IEEE 1149.1 Data Register scan);
> + SDR (Scan Data Register, IEEE 1149.1 Instruction Register scan);
> + RUNTEST (Forces IEEE 1149.1 bus to a run state for specified
> + number of clocks).
> +
> + If you want this support, you should say Y here.
> +
> + To compile this driver as a module, choose M here: the module will
> + be called jtag.
> diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile
> new file mode 100644
> index 0000000..af37493
> --- /dev/null
> +++ b/drivers/jtag/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_JTAG) += jtag.o
> diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c
> new file mode 100644
> index 0000000..f948d4c
> --- /dev/null
> +++ b/drivers/jtag/jtag.c
> @@ -0,0 +1,312 @@
> +/*
> + * drivers/jtag/jtag.c
> + *
> + * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2017 Oleksandr Shamray <oleksandrs@mellanox.com>
> + *
> + * Released under the GPLv2 only.
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <linux/cdev.h>
> +#include <linux/device.h>
> +#include <linux/jtag.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/rtnetlink.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <uapi/linux/jtag.h>
> +
> +struct jtag {
> + struct list_head list;
> + struct device *dev;
> + struct cdev cdev;
> + int id;
> + spinlock_t lock;
> + int open;
> + const struct jtag_ops *ops;
> + unsigned long priv[0] __aligned(ARCH_DMA_MINALIGN);
> +};
> +
> +static dev_t jtag_devt;
> +static LIST_HEAD(jtag_list);
> +static DEFINE_MUTEX(jtag_mutex);
> +static DEFINE_IDA(jtag_ida);
> +
> +void *jtag_priv(struct jtag *jtag)
> +{
> + return jtag->priv;
> +}
> +EXPORT_SYMBOL_GPL(jtag_priv);
> +
> +static __u64 jtag_copy_from_user(__u64 udata, unsigned long bit_size)
> +{
> + unsigned long size;
> + void *kdata;
> +
> + size = DIV_ROUND_UP(bit_size, BITS_PER_BYTE);
> + kdata = memdup_user(u64_to_user_ptr(udata), size);
> +
> + return (__u64)(uintptr_t)kdata;
> +}
> +
> +static unsigned long jtag_copy_to_user(__u64 udata, __u64 kdata,
> + unsigned long bit_size)
> +{
> + unsigned long size;
> +
> + size = DIV_ROUND_UP(bit_size, BITS_PER_BYTE);
> +
> + return copy_to_user(u64_to_user_ptr(udata), jtag_u64_to_ptr(kdata),
> + size);
> +}
> +
> +static struct class jtag_class = {
> + .name = "jtag",
> + .owner = THIS_MODULE,
> +};
> +
> +static int jtag_run_test_idle_op(struct jtag *jtag,
> + struct jtag_run_test_idle *idle)
> +{
> + if (jtag->ops->idle)
> + return jtag->ops->idle(jtag, idle);
> + else
> + return -EOPNOTSUPP;
> +}
> +
> +static int jtag_xfer_op(struct jtag *jtag, struct jtag_xfer *xfer)
> +{
> + if (jtag->ops->xfer)
> + return jtag->ops->xfer(jtag, xfer);
> + else
> + return -EOPNOTSUPP;
> +}
> +
> +static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> + struct jtag *jtag = file->private_data;
> + __u32 *uarg = (__u32 __user *)arg;
> + void *varg = (void __user *)arg;
> + struct jtag_run_test_idle idle;
> + struct jtag_xfer xfer;
> + __u64 tdio_user;
> + __u32 value;
> + int err;
> +
> + switch (cmd) {
> + case JTAG_GIOCFREQ:
> + if (jtag->ops->freq_get)
> + err = jtag->ops->freq_get(jtag, &value);
> + else
> + err = -EOPNOTSUPP;
> + if (err)
> + break;
> +
> + err = put_user(value, uarg);
> + break;
> +
> + case JTAG_SIOCFREQ:
> + err = __get_user(value, uarg);
> +
> + if (value == 0)
> + err = -EINVAL;
> + if (err)
> + break;
> +
> + if (jtag->ops->freq_set)
> + err = jtag->ops->freq_set(jtag, value);
> + else
> + err = -EOPNOTSUPP;
> + break;
> +
> + case JTAG_IOCRUNTEST:
> + if (copy_from_user(&idle, varg,
> + sizeof(struct jtag_run_test_idle)))
> + return -ENOMEM;
> + err = jtag_run_test_idle_op(jtag, &idle);
> + break;
> +
> + case JTAG_IOCXFER:
> + if (copy_from_user(&xfer, varg, sizeof(struct jtag_xfer)))
> + return -EFAULT;
> +
> + if (xfer.length >= JTAG_MAX_XFER_DATA_LEN)
> + return -EFAULT;
> +
> + tdio_user = xfer.tdio;
> + xfer.tdio = jtag_copy_from_user(xfer.tdio, xfer.length);
> + if (!xfer.tdio)
> + return -ENOMEM;
> +
> + err = jtag_xfer_op(jtag, &xfer);
> + if (jtag_copy_to_user(tdio_user, xfer.tdio, xfer.length)) {
> + kfree(jtag_u64_to_ptr(xfer.tdio));
> + return -EFAULT;
> + }
> +
> + kfree(jtag_u64_to_ptr(xfer.tdio));
> + xfer.tdio = tdio_user;
> + if (copy_to_user(varg, &xfer, sizeof(struct jtag_xfer)))
> + return -EFAULT;
> + break;
> +
> + case JTAG_GIOCSTATUS:
> + if (jtag->ops->status_get)
> + err = jtag->ops->status_get(jtag, &value);
> + else
> + err = -EOPNOTSUPP;
> + if (err)
> + break;
> +
> + err = put_user(value, uarg);
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> + return err;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long jtag_ioctl_compat(struct file *file, unsigned int cmd,
> + unsigned long arg)
> +{
> + return jtag_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> +}
> +#endif
> +
> +static int jtag_open(struct inode *inode, struct file *file)
> +{
> + struct jtag *jtag = container_of(inode->i_cdev, struct jtag, cdev);
> +
> + spin_lock(&jtag->lock);
> +
> + if (jtag->open) {
> + dev_info(NULL, "jtag already opened\n");
> + spin_unlock(&jtag->lock);
> + return -EBUSY;
> + }
> +
> + jtag->open++;
> + file->private_data = jtag;
> + spin_unlock(&jtag->lock);
> + return 0;
> +}
> +
> +static int jtag_release(struct inode *inode, struct file *file)
> +{
> + struct jtag *jtag = file->private_data;
> +
> + spin_lock(&jtag->lock);
> + jtag->open--;
> + spin_unlock(&jtag->lock);
> + return 0;
> +}
> +
> +static const struct file_operations jtag_fops = {
> + .owner = THIS_MODULE,
> + .open = jtag_open,
> + .release = jtag_release,
> + .llseek = noop_llseek,
> + .unlocked_ioctl = jtag_ioctl,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl = jtag_ioctl_compat,
> +#endif
> +};
> +
> +struct jtag *jtag_alloc(size_t priv_size, const struct jtag_ops *ops)
> +{
> + struct jtag *jtag;
> +
> + jtag = kzalloc(sizeof(*jtag) + round_up(priv_size, ARCH_DMA_MINALIGN),
> + GFP_KERNEL);
> + if (!jtag)
> + return NULL;
> +
> + jtag->ops = ops;
> + return jtag;
> +}
> +EXPORT_SYMBOL_GPL(jtag_alloc);
> +
> +void jtag_free(struct jtag *jtag)
> +{
> + kfree(jtag);
> +}
> +EXPORT_SYMBOL_GPL(jtag_free);
> +
> +int jtag_register(struct jtag *jtag)
> +{
> + int id;
> + int err;
> +
> + id = ida_simple_get(&jtag_ida, 0, 0, GFP_KERNEL);
> + if (id < 0)
> + return id;
> +
> + jtag->id = id;
> + cdev_init(&jtag->cdev, &jtag_fops);
> + jtag->cdev.owner = THIS_MODULE;
> + err = cdev_add(&jtag->cdev, MKDEV(MAJOR(jtag_devt), jtag->id), 1);
> + if (err)
> + goto err_cdev;
> +
> + /* Register this jtag device with the driver core */
> + jtag->dev = device_create(&jtag_class, NULL, MKDEV(MAJOR(jtag_devt),
> + jtag->id),
> + NULL, "jtag%d", jtag->id);
> + if (!jtag->dev)
> + goto err_device_create;
> +
> + jtag->open = 0;
> + dev_set_drvdata(jtag->dev, jtag);
> + spin_lock_init(&jtag->lock);
> + mutex_lock(&jtag_mutex);
> + list_add_tail(&jtag->list, &jtag_list);
> + mutex_unlock(&jtag_mutex);
> + return err;
> +
> +err_device_create:
> + cdev_del(&jtag->cdev);
> +err_cdev:
> + ida_simple_remove(&jtag_ida, id);
> + return err;
> +}
> +EXPORT_SYMBOL_GPL(jtag_register);
> +
> +void jtag_unregister(struct jtag *jtag)
> +{
> + struct device *dev = jtag->dev;
> +
> + mutex_lock(&jtag_mutex);
> + list_del(&jtag->list);
> + mutex_unlock(&jtag_mutex);
> + cdev_del(&jtag->cdev);
> + device_unregister(dev);
> + ida_simple_remove(&jtag_ida, jtag->id);
> +}
> +EXPORT_SYMBOL_GPL(jtag_unregister);
> +
> +static int __init jtag_init(void)
> +{
> + int err;
> +
> + err = alloc_chrdev_region(&jtag_devt, 0, 1, "jtag");
> + if (err)
> + return err;
> + return class_register(&jtag_class);
> +}
> +
> +static void __exit jtag_exit(void)
> +{
> + class_unregister(&jtag_class);
> +}
> +
> +module_init(jtag_init);
> +module_exit(jtag_exit);
> +
> +MODULE_AUTHOR("Oleksandr Shamray <oleksandrs@mellanox.com>");
> +MODULE_DESCRIPTION("Generic jtag support");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/jtag.h b/include/linux/jtag.h
> new file mode 100644
> index 0000000..f48ae9d
> --- /dev/null
> +++ b/include/linux/jtag.h
> @@ -0,0 +1,48 @@
> +/*
> + * drivers/jtag/jtag.c
> + *
> + * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2017 Oleksandr Shamray <oleksandrs@mellanox.com>
> + *
> + * Released under the GPLv2 only.
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef __JTAG_H
> +#define __JTAG_H
> +
> +#include <uapi/linux/jtag.h>
> +
> +#ifndef ARCH_DMA_MINALIGN
> +#define ARCH_DMA_MINALIGN 1
> +#endif
> +
> +#define jtag_u64_to_ptr(arg) ((void *)(uintptr_t)arg)
> +
> +#define JTAG_MAX_XFER_DATA_LEN 65535
> +
> +struct jtag;
> +/**
> + * struct jtag_ops - callbacks for jtag control functions:
> + *
> + * @freq_get: get frequency function. Filled by device driver
> + * @freq_set: set frequency function. Filled by device driver
> + * @status_get: set status function. Filled by device driver
> + * @idle: set JTAG to idle state function. Filled by device driver
> + * @xfer: send JTAG xfer function. Filled by device driver
> + */
> +struct jtag_ops {
> + int (*freq_get)(struct jtag *jtag, __u32 *freq);
> + int (*freq_set)(struct jtag *jtag, __u32 freq);
> + int (*status_get)(struct jtag *jtag, __u32 *state);
> + int (*idle)(struct jtag *jtag, struct jtag_run_test_idle *idle);
> + int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer);
> +};
> +
> +void *jtag_priv(struct jtag *jtag);
> +int jtag_register(struct jtag *jtag);
> +void jtag_unregister(struct jtag *jtag);
> +struct jtag *jtag_alloc(size_t priv_size, const struct jtag_ops *ops);
> +void jtag_free(struct jtag *jtag);
> +
> +#endif /* __JTAG_H */
> diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h
> new file mode 100644
> index 0000000..af22ea6
> --- /dev/null
> +++ b/include/uapi/linux/jtag.h
> @@ -0,0 +1,111 @@
> +/*
> + * JTAG class driver
> + *
> + * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2017 Oleksandr Shamray <oleksandrs@mellanox.com>
> + *
> + * Released under the GPLv2/BSD.
> + * SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
> + */
> +
> +#ifndef __UAPI_LINUX_JTAG_H
> +#define __UAPI_LINUX_JTAG_H
> +
> +/**
> + * enum jtag_xfer_mode:
> + *
> + * @JTAG_XFER_HW_MODE: hardware mode transfer
> + * @JTAG_XFER_SW_MODE: software mode transfer
> + */
> +enum jtag_xfer_mode {
> + JTAG_XFER_HW_MODE,
> + JTAG_XFER_SW_MODE,
> +};
> +
> +/**
> + * enum jtag_endstate:
> + *
> + * @JTAG_STATE_IDLE: JTAG state machine IDLE state
> + * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state
> + * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state
> + */
> +enum jtag_endstate {
> + JTAG_STATE_IDLE,
> + JTAG_STATE_PAUSEIR,
> + JTAG_STATE_PAUSEDR,
> +};
> +
> +/**
> + * enum jtag_xfer_type:
> + *
> + * @JTAG_SIR_XFER: SIR transfer
> + * @JTAG_SDR_XFER: SDR transfer
> + */
> +enum jtag_xfer_type {
> + JTAG_SIR_XFER,
> + JTAG_SDR_XFER,
> +};
> +
> +/**
> + * enum jtag_xfer_direction:
> + *
> + * @JTAG_READ_XFER: read transfer
> + * @JTAG_WRITE_XFER: write transfer
> + */
> +enum jtag_xfer_direction {
> + JTAG_READ_XFER,
> + JTAG_WRITE_XFER,
> +};
> +
> +/**
> + * struct jtag_run_test_idle - forces JTAG state machine to
> + * RUN_TEST/IDLE state
> + *
> + * @mode: access mode
> + * @reset: 0 - run IDLE/PAUSE from current state
> + * 1 - go through TEST_LOGIC/RESET state before IDLE/PAUSE
> + * @end: completion flag
> + * @tck: clock counter
> + *
> + * Structure represents interface to JTAG device for jtag idle
> + * execution.
> + */
> +struct jtag_run_test_idle {
> + __u8 mode;
> + __u8 reset;
> + __u8 endstate;
> + __u8 tck;
> +};
> +
> +/**
> + * struct jtag_xfer - jtag xfer:
> + *
> + * @mode: access mode
> + * @type: transfer type
> + * @direction: xfer direction
> + * @length: xfer bits len
> + * @tdio : xfer data array
> + * @endir: xfer end state
> + *
> + * Structure represents interface to Aspeed JTAG device for jtag sdr xfer
> + * execution.
> + */
> +struct jtag_xfer {
> + __u8 mode;
> + __u8 type;
> + __u8 direction;
> + __u8 endstate;
> + __u32 length;
> + __u64 tdio;
> +};
> +
> +#define __JTAG_IOCTL_MAGIC 0xb2
> +
> +#define JTAG_IOCRUNTEST _IOW(__JTAG_IOCTL_MAGIC, 0,\
> + struct jtag_run_test_idle)
> +#define JTAG_SIOCFREQ _IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int)
> +#define JTAG_GIOCFREQ _IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int)
> +#define JTAG_IOCXFER _IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer)
> +#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtag_endstate)
> +
> +#endif /* __UAPI_LINUX_JTAG_H */
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html

Thanks,

Moritz

\
 
 \ /
  Last update: 2017-09-01 19:49    [W:0.106 / U:16.340 seconds]
©2003-2017 Jasper Spaans. hosted at Digital OceanAdvertise on this site