Received: by 2002:ac0:a5b6:0:0:0:0:0 with SMTP id m51-v6csp3147121imm; Tue, 29 May 2018 01:45:33 -0700 (PDT) X-Google-Smtp-Source: AB8JxZqv3wdBpdQRZmLDrlWpa/Lx73MjqMvoWnhDxyHZ73JcVWdhoaFBZad1xM8eQbcK9it4yIm8 X-Received: by 2002:aa7:8009:: with SMTP id j9-v6mr16596148pfi.52.1527583533867; Tue, 29 May 2018 01:45:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1527583533; cv=none; d=google.com; s=arc-20160816; b=D3YJikN63m7Zq5jROQqMzieZYQ0NHRo+YpKzoqo73deAhVO+921gh4WRtrd9BYIjsE DN5yXOaCLrxGMxjb5Ufg0QKwTa5ljy6Xhs0Zy10ImgfG2ogIxD0GW3HoJU6LUHMT5COu wbTlU4FvU2idHNfTwipQnClRgzYysPROd36LzZc40uiveF1aVuuxuKz3VNVzUe2LQBex eovUSxjaVrx2zgVcQRY2mUNrO8eNqFvGPcNd4SBD6Bk3zg4c06Da2gyf/V7bFYga7LWR suRJ+O8fOlPtuP2+1nsbnQ7EPLZ8oA8lgm+xIAnP8Le3LDNZGHg0/Puwr3uiUwRyJYF9 GFQQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-disposition:mime-version:references:mail-followup-to :message-id:subject:to:from:date:dkim-signature :arc-authentication-results; bh=wQh3Vwz91aUOH8dD7jES71YaH3diETR1v9P45q3fsn0=; b=sw/svDCJ6yVcJTTcrzQoEBtUsuxV0/8dXTucEyLUaIP1TYSlk7OqpIQOeLG/Y/qaho Cntp1pOYM5NvZ0Skpys4jQj88oA8fBAs7pJq4T/7BgbJkyRIUQGi6AbODgsTixDP7k40 GmuzZpfyP2Rc8fc/BI/LJSzTdGuzanmgtC/vGeY+nhM1zFr6muOl+vcj7QNcsZ3uY6oL KPA7lTAtH5vSkcXcDtB3iajp6pcu7JBCQt+rmrh7zxJ0sSfkrxU2jtRNhCsw4O5i2zfF JQxjqQAriB5WKyS35wcnntUUnC8enhmgTF4Pa5FLHHMaX4LuuahFWAg0v9P4nWUqZ8Sf qXwg== ARC-Authentication-Results: i=1; mx.google.com; dkim=fail header.i=@ffwll.ch header.s=google header.b=OjOtFsXT; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id z6-v6si14352981pgp.102.2018.05.29.01.45.19; Tue, 29 May 2018 01:45:33 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=fail header.i=@ffwll.ch header.s=google header.b=OjOtFsXT; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932449AbeE2IoP (ORCPT + 99 others); Tue, 29 May 2018 04:44:15 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:54947 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755307AbeE2IoL (ORCPT ); Tue, 29 May 2018 04:44:11 -0400 Received: by mail-wm0-f66.google.com with SMTP id f6-v6so38154159wmc.4 for ; Tue, 29 May 2018 01:44:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ffwll.ch; s=google; h=sender:date:from:to:subject:message-id:mail-followup-to:references :mime-version:content-disposition:in-reply-to:user-agent; bh=wQh3Vwz91aUOH8dD7jES71YaH3diETR1v9P45q3fsn0=; b=OjOtFsXTCPnmHbXJ9w3DP0+tMydRZVGSzZb1kIUJeAmZIPJnNLFyMTh2oZy6DA7GN/ 2i+akgPtaNMFke2E6JFLgkmpSBDMeSnqCjtBVnHqmfQ60PjUUFYPIMaamVB1aiYfKrDD B3DcVUkNCNruaRpxp+MOVYdn2IujKzFk3rDlU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:from:to:subject:message-id :mail-followup-to:references:mime-version:content-disposition :in-reply-to:user-agent; bh=wQh3Vwz91aUOH8dD7jES71YaH3diETR1v9P45q3fsn0=; b=cnkt29ZWm4Ft8qVn5WnAR/rceOwurmv0cyxG5AYBlEA4Pge15lfTqAjN4pbIebDW5C Vn5ocYR/ME8teZj6wZmjes5iTYzOiGXbG+m0vQZahpqaZmMyYXJmyeru57kZCLVNRMkk Gn8nmsn9jnidTeLHGhYEXSOfWaOZ89g0WNxf7l+/+OyYVHMWuPq3Qj5opJR6ufz9V8FW GMiIeInShHi6/MZsCALC8Y1wh8uKl3HlaYCviqZ9ZsqRn9ZtIorMHpVOiZwcNZrie+hc LPF7CwZ3LqbLhhWWvQB2R7HaF38JlQc+BMCAt20dlgowT3BzMbBl5rNx/QfsIT3RnM+C xe5Q== X-Gm-Message-State: ALKqPwdLyuDf1b91J77g3HykKACijVZmkCutfdm+YCRtA1uqky8evorm +AWGt03DPHPJlWXL6Hmu0vrgLQ== X-Received: by 2002:a50:fb96:: with SMTP id e22-v6mr17999513edq.87.1527583449638; Tue, 29 May 2018 01:44:09 -0700 (PDT) Received: from phenom.ffwll.local ([2a02:168:5628:0:d0c7:bcda:eea:9e5d]) by smtp.gmail.com with ESMTPSA id o22-v6sm8250322edr.75.2018.05.29.01.44.08 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 29 May 2018 01:44:08 -0700 (PDT) Date: Tue, 29 May 2018 10:44:06 +0200 From: Daniel Vetter To: Gerd Hoffmann , dri-devel@lists.freedesktop.org, David Airlie , Tomeu Vizoso , Sumit Semwal , Shuah Khan , open list , "open list:DMA BUFFER SHARING FRAMEWORK" , "moderated list:DMA BUFFER SHARING FRAMEWORK" , "open list:KERNEL SELFTEST FRAMEWORK" Subject: Re: [PATCH v3] Add udmabuf misc device Message-ID: <20180529084406.GI3438@phenom.ffwll.local> Mail-Followup-To: Gerd Hoffmann , dri-devel@lists.freedesktop.org, David Airlie , Tomeu Vizoso , Sumit Semwal , Shuah Khan , open list , "open list:DMA BUFFER SHARING FRAMEWORK" , "moderated list:DMA BUFFER SHARING FRAMEWORK" , "open list:KERNEL SELFTEST FRAMEWORK" References: <20180525140808.12714-1-kraxel@redhat.com> <20180529082327.GF3438@phenom.ffwll.local> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20180529082327.GF3438@phenom.ffwll.local> X-Operating-System: Linux phenom 4.15.0-3-amd64 User-Agent: Mutt/1.9.5 (2018-04-13) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Tue, May 29, 2018 at 10:23:27AM +0200, Daniel Vetter wrote: > On Fri, May 25, 2018 at 04:08:08PM +0200, Gerd Hoffmann wrote: > > A driver to let userspace turn memfd regions into dma-bufs. > > > > Use case: Allows qemu create dmabufs for the vga framebuffer or > > virtio-gpu ressources. Then they can be passed around to display > > those guest things on the host. To spice client for classic full > > framebuffer display, and hopefully some day to wayland server for > > seamless guest window display. > > > > Note: Initial revision which supports a single region only so it > > can't handle virtio-gpu ressources yet. > > > > qemu test branch: > > https://git.kraxel.org/cgit/qemu/log/?h=sirius/udmabuf > > > > Cc: David Airlie > > Cc: Tomeu Vizoso > > Cc: Daniel Vetter > > Signed-off-by: Gerd Hoffmann > > --- > > include/uapi/linux/udmabuf.h | 19 ++ > > drivers/dma-buf/udmabuf.c | 240 ++++++++++++++++++++++ > > tools/testing/selftests/drivers/dma-buf/udmabuf.c | 95 +++++++++ > > drivers/dma-buf/Kconfig | 7 + > > drivers/dma-buf/Makefile | 1 + > > tools/testing/selftests/drivers/dma-buf/Makefile | 5 + > > 6 files changed, 367 insertions(+) > > create mode 100644 include/uapi/linux/udmabuf.h > > create mode 100644 drivers/dma-buf/udmabuf.c > > create mode 100644 tools/testing/selftests/drivers/dma-buf/udmabuf.c > > create mode 100644 tools/testing/selftests/drivers/dma-buf/Makefile > > > > diff --git a/include/uapi/linux/udmabuf.h b/include/uapi/linux/udmabuf.h > > new file mode 100644 > > index 0000000000..2fbe69cf05 > > --- /dev/null > > +++ b/include/uapi/linux/udmabuf.h > > @@ -0,0 +1,19 @@ > > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > > +#ifndef _UAPI_LINUX_UDMABUF_H > > +#define _UAPI_LINUX_UDMABUF_H > > + > > +#include > > +#include > > + > > +#define UDMABUF_FLAGS_CLOEXEC 0x01 > > + > > +struct udmabuf_create { > > + __u32 memfd; > > + __u32 flags; > > + __u64 offset; > > + __u64 size; > > +}; > > + > > +#define UDMABUF_CREATE _IOW(0x42, 0x23, struct udmabuf_create) > > + > > +#endif /* _UAPI_LINUX_UDMABUF_H */ > > diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c > > new file mode 100644 > > index 0000000000..f9600dc985 > > --- /dev/null > > +++ b/drivers/dma-buf/udmabuf.c > > @@ -0,0 +1,240 @@ > > +/* > > + * This program is free software; you can redistribute it and/or modify > > + * it under the terms of the GNU General Public License version 2 as > > + * published by the Free Software Foundation. > > + */ > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +#include > > + > > +struct udmabuf { > > + struct file *filp; > > + u32 pagecount; > > + struct page **pages; > > +}; > > + > > +static int udmabuf_vm_fault(struct vm_fault *vmf) > > +{ > > + struct vm_area_struct *vma = vmf->vma; > > + struct udmabuf *ubuf = vma->vm_private_data; > > + > > + if (WARN_ON(vmf->pgoff >= ubuf->pagecount)) > > + return VM_FAULT_SIGBUS; > > + > > + vmf->page = ubuf->pages[vmf->pgoff]; > > + get_page(vmf->page); > > + return 0; > > +} > > + > > +static const struct vm_operations_struct udmabuf_vm_ops = { > > + .fault = udmabuf_vm_fault, > > +}; > > + > > +static int mmap_udmabuf(struct dma_buf *buf, struct vm_area_struct *vma) > > +{ > > + struct udmabuf *ubuf = buf->priv; > > + > > + if ((vma->vm_flags & VM_SHARED) == 0) > > + return -EINVAL; > > + > > + vma->vm_ops = &udmabuf_vm_ops; > > + vma->vm_private_data = ubuf; > > + return 0; > > +} > > + > > +static struct sg_table *map_udmabuf(struct dma_buf_attachment *at, > > + enum dma_data_direction direction) > > +{ > > + struct udmabuf *ubuf = at->dmabuf->priv; > > + struct sg_table *sg; > > + > > + sg = kzalloc(sizeof(*sg), GFP_KERNEL); > > + if (!sg) > > + goto err1; > > + if (sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->pagecount, > > + 0, ubuf->pagecount << PAGE_SHIFT, > > + GFP_KERNEL) < 0) > > + goto err2; > > + if (!dma_map_sg(at->dev, sg->sgl, sg->nents, direction)) > > + goto err3; > > + > > + return sg; > > + > > +err3: > > + sg_free_table(sg); > > +err2: > > + kfree(sg); > > +err1: > > + return ERR_PTR(-ENOMEM); > > +} > > + > > +static void unmap_udmabuf(struct dma_buf_attachment *at, > > + struct sg_table *sg, > > + enum dma_data_direction direction) > > +{ > > + sg_free_table(sg); > > + kfree(sg); > > +} > > + > > +static void release_udmabuf(struct dma_buf *buf) > > +{ > > + struct udmabuf *ubuf = buf->priv; > > + pgoff_t pg; > > + > > + for (pg = 0; pg < ubuf->pagecount; pg++) > > + put_page(ubuf->pages[pg]); > > + fput(ubuf->filp); > > + kfree(ubuf->pages); > > + kfree(ubuf); > > +} > > + > > +static void *kmap_atomic_udmabuf(struct dma_buf *buf, unsigned long page_num) > > +{ > > + struct udmabuf *ubuf = buf->priv; > > + struct page *page = ubuf->pages[page_num]; > > + > > + return kmap_atomic(page); > > +} > > + > > +static void *kmap_udmabuf(struct dma_buf *buf, unsigned long page_num) > > +{ > > + struct udmabuf *ubuf = buf->priv; > > + struct page *page = ubuf->pages[page_num]; > > + > > + return kmap(page); > > +} > > The above leaks like mad since no kunamp? > > Also I think we have 0 users of the kmap atomic interfaces ... so not sure > whether it's worth it to implement those. > -Daniel > > > + > > +static struct dma_buf_ops udmabuf_ops = { > > + .map_dma_buf = map_udmabuf, > > + .unmap_dma_buf = unmap_udmabuf, > > + .release = release_udmabuf, > > + .map_atomic = kmap_atomic_udmabuf, > > + .map = kmap_udmabuf, > > + .mmap = mmap_udmabuf, > > +}; > > + > > +static long udmabuf_ioctl_create(struct file *filp, unsigned long arg) > > +{ > > + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); > > + struct udmabuf_create create; > > + struct udmabuf *ubuf; > > + struct dma_buf *buf; > > + pgoff_t pgoff, pgidx; > > + struct page *page; > > + int ret = -EINVAL; > > + u32 flags; > > + > > + if (copy_from_user(&create, (void __user *)arg, > > + sizeof(struct udmabuf_create))) > > + return -EFAULT; > > + > > + if (!IS_ALIGNED(create.offset, PAGE_SIZE)) > > + return -EINVAL; > > + if (!IS_ALIGNED(create.size, PAGE_SIZE)) > > + return -EINVAL; > > + > > + ubuf = kzalloc(sizeof(struct udmabuf), GFP_KERNEL); > > + if (!ubuf) > > + return -ENOMEM; > > + > > + ubuf->filp = fget(create.memfd); > > + if (!ubuf->filp) > > + goto err_free_ubuf; > > + if (!shmem_mapping(file_inode(ubuf->filp)->i_mapping)) > > + goto err_free_ubuf; Can/should we test here that the memfd has a locked down size here? I do think yes (otherwise the dma-buf operations need to take that into account, and all kinds of funny things might happen), but I'm not sure whether your userspace can cope with that. On that: Link to userspace patches/git tree using this would be nice. -Daniel > > + > > + ubuf->pagecount = create.size >> PAGE_SHIFT; > > + ubuf->pages = kmalloc_array(ubuf->pagecount, sizeof(struct page*), > > + GFP_KERNEL); > > + if (!ubuf->pages) { > > + ret = -ENOMEM; > > + goto err_free_ubuf; > > + } > > + > > + pgoff = create.offset >> PAGE_SHIFT; > > + for (pgidx = 0; pgidx < ubuf->pagecount; pgidx++) { > > + page = shmem_read_mapping_page( > > + file_inode(ubuf->filp)->i_mapping, pgoff + pgidx); > > + if (IS_ERR(page)) { > > + ret = PTR_ERR(buf); > > + goto err_put_pages; > > + } > > + ubuf->pages[pgidx] = page; > > + } > > + > > + exp_info.ops = &udmabuf_ops; > > + exp_info.size = ubuf->pagecount << PAGE_SHIFT; > > + exp_info.priv = ubuf; > > + > > + buf = dma_buf_export(&exp_info); > > + if (IS_ERR(buf)) { > > + ret = PTR_ERR(buf); > > + goto err_put_pages; > > + } > > + > > + flags = 0; > > + if (create.flags & UDMABUF_FLAGS_CLOEXEC) > > + flags |= O_CLOEXEC; > > + return dma_buf_fd(buf, flags); > > + > > +err_put_pages: > > + while (pgidx > 0) > > + put_page(ubuf->pages[--pgidx]); > > +err_free_ubuf: > > + fput(ubuf->filp); > > + kfree(ubuf->pages); > > + kfree(ubuf); > > + return ret; > > +} > > + > > +static long udmabuf_ioctl(struct file *filp, unsigned int ioctl, > > + unsigned long arg) > > +{ > > + long ret; > > + > > + switch (ioctl) { > > + case UDMABUF_CREATE: > > + ret = udmabuf_ioctl_create(filp, arg); > > + break; > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + return ret; > > +} > > + > > +static const struct file_operations udmabuf_fops = { > > + .owner = THIS_MODULE, > > + .unlocked_ioctl = udmabuf_ioctl, > > +}; > > + > > +static struct miscdevice udmabuf_misc = { > > + .minor = MISC_DYNAMIC_MINOR, > > + .name = "udmabuf", > > + .fops = &udmabuf_fops, > > +}; > > + > > +static int __init udmabuf_dev_init(void) > > +{ > > + return misc_register(&udmabuf_misc); > > +} > > + > > +static void __exit udmabuf_dev_exit(void) > > +{ > > + misc_deregister(&udmabuf_misc); > > +} > > + > > +module_init(udmabuf_dev_init) > > +module_exit(udmabuf_dev_exit) > > + > > +MODULE_AUTHOR("Gerd Hoffmann "); > > +MODULE_LICENSE("GPL v2"); > > diff --git a/tools/testing/selftests/drivers/dma-buf/udmabuf.c b/tools/testing/selftests/drivers/dma-buf/udmabuf.c > > new file mode 100644 > > index 0000000000..d46c58b0dd > > --- /dev/null > > +++ b/tools/testing/selftests/drivers/dma-buf/udmabuf.c > > @@ -0,0 +1,95 @@ > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +#include > > +#include > > +#include > > +#include > > + > > +#define TEST_PREFIX "drivers/dma-buf/udmabuf" > > +#define NUM_PAGES 4 > > + > > +static int memfd_create(const char *name, unsigned int flags) > > +{ > > + return syscall(__NR_memfd_create, name, flags); > > +} > > + > > +int main(int argc, char *argv[]) > > +{ > > + struct udmabuf_create create; > > + int devfd, memfd, buf, ret; > > + off_t size; > > + void *mem; > > + > > + devfd = open("/dev/udmabuf", O_RDWR); > > + if (devfd < 0) { > > + printf("%s: [skip,no-udmabuf]\n", TEST_PREFIX); > > + exit(77); > > + } > > + > > + memfd = memfd_create("udmabuf-test", MFD_CLOEXEC); > > + if (memfd < 0) { > > + printf("%s: [skip,no-memfd]\n", TEST_PREFIX); > > + exit(77); > > + } > > + > > + size = getpagesize() * NUM_PAGES; > > + ret = ftruncate(memfd, size); > > + if (ret == -1) { > > + printf("%s: [FAIL,memfd-truncate]\n", TEST_PREFIX); > > + exit(1); > > + } > > + > > + memset(&create, 0, sizeof(create)); > > + > > + /* should fail (offset not page aligned) */ > > + create.memfd = memfd; > > + create.offset = getpagesize()/2; > > + create.size = getpagesize(); > > + buf = ioctl(devfd, UDMABUF_CREATE, &create); > > + if (buf >= 0) { > > + printf("%s: [FAIL,test-1]\n", TEST_PREFIX); > > + exit(1); > > + } > > + > > + /* should fail (size not multiple of page) */ > > + create.memfd = memfd; > > + create.offset = 0; > > + create.size = getpagesize()/2; > > + buf = ioctl(devfd, UDMABUF_CREATE, &create); > > + if (buf >= 0) { > > + printf("%s: [FAIL,test-2]\n", TEST_PREFIX); > > + exit(1); > > + } > > + > > + /* should fail (not memfd) */ > > + create.memfd = 0; /* stdin */ > > + create.offset = 0; > > + create.size = size; > > + buf = ioctl(devfd, UDMABUF_CREATE, &create); > > + if (buf >= 0) { > > + printf("%s: [FAIL,test-3]\n", TEST_PREFIX); > > + exit(1); > > + } > > + > > + /* should work */ > > + create.memfd = memfd; > > + create.offset = 0; > > + create.size = size; > > + buf = ioctl(devfd, UDMABUF_CREATE, &create); > > + if (buf < 0) { > > + printf("%s: [FAIL,test-4]\n", TEST_PREFIX); > > + exit(1); > > + } > > + > > + fprintf(stderr, "%s: ok\n", TEST_PREFIX); > > + close(buf); > > + close(memfd); > > + close(devfd); > > + return 0; > > +} > > diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig > > index ed3b785bae..19be3ec62d 100644 > > --- a/drivers/dma-buf/Kconfig > > +++ b/drivers/dma-buf/Kconfig > > @@ -30,4 +30,11 @@ config SW_SYNC > > WARNING: improper use of this can result in deadlocking kernel > > drivers from userspace. Intended for test and debug only. > > > > +config UDMABUF > > + bool "userspace dmabuf misc driver" > > + default n > > + depends on DMA_SHARED_BUFFER > > + ---help--- > > + A driver to let userspace turn iovs into dma-bufs. > > + > > endmenu > > diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile > > index c33bf88631..0913a6ccab 100644 > > --- a/drivers/dma-buf/Makefile > > +++ b/drivers/dma-buf/Makefile > > @@ -1,3 +1,4 @@ > > obj-y := dma-buf.o dma-fence.o dma-fence-array.o reservation.o seqno-fence.o > > obj-$(CONFIG_SYNC_FILE) += sync_file.o > > obj-$(CONFIG_SW_SYNC) += sw_sync.o sync_debug.o > > +obj-$(CONFIG_UDMABUF) += udmabuf.o > > diff --git a/tools/testing/selftests/drivers/dma-buf/Makefile b/tools/testing/selftests/drivers/dma-buf/Makefile > > new file mode 100644 > > index 0000000000..4154c3d7aa > > --- /dev/null > > +++ b/tools/testing/selftests/drivers/dma-buf/Makefile > > @@ -0,0 +1,5 @@ > > +CFLAGS += -I../../../../../usr/include/ > > + > > +TEST_GEN_PROGS := udmabuf > > + > > +include ../../lib.mk > > -- > > 2.9.3 > > > > -- > Daniel Vetter > Software Engineer, Intel Corporation > http://blog.ffwll.ch -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch