Merge branch 'topic-0620b/samsung-3d-3.4' into chromeos-exynos-3.4
authorOlof Johansson <olofj@chromium.org>
Wed, 20 Jun 2012 18:39:10 +0000 (11:39 -0700)
committerOlof Johansson <olofj@chromium.org>
Wed, 20 Jun 2012 18:39:10 +0000 (11:39 -0700)
* topic-0620b/samsung-3d-3.4:
  drm: vithar: don't default to enabled
  arm: exynos5: Add G3D physical address on Exynos5
  arm: exynos5: Add irq definitions for GPU
  gpu: vithar: Add DVFS in Kconfig and fix compilation issues
  gpu: vithar: Fix for deadlock in mali kbase.
  gpu: vithar: Removal of UMP dependent code from Vithar
  EXYNOS5: GPU: Makefile hack for vithar
  gpu: vithar: Add vithar code base to kernel

86 files changed:
Documentation/DMA-attributes.txt
Documentation/DocBook/media/v4l/compat.xml
Documentation/DocBook/media/v4l/io.xml
Documentation/DocBook/media/v4l/v4l2.xml
Documentation/DocBook/media/v4l/vidioc-create-bufs.xml
Documentation/DocBook/media/v4l/vidioc-expbuf.xml [new file with mode: 0644]
Documentation/DocBook/media/v4l/vidioc-qbuf.xml
Documentation/DocBook/media/v4l/vidioc-reqbufs.xml
Documentation/dma-buf-sharing.txt
Documentation/kernel-parameters.txt
Documentation/video4linux/v4l2-framework.txt
arch/Kconfig
arch/arm/Kconfig
arch/arm/common/dmabounce.c
arch/arm/include/asm/device.h
arch/arm/include/asm/dma-contiguous.h [new file with mode: 0644]
arch/arm/include/asm/dma-iommu.h [new file with mode: 0644]
arch/arm/include/asm/dma-mapping.h
arch/arm/include/asm/mach/map.h
arch/arm/kernel/setup.c
arch/arm/mm/dma-mapping.c
arch/arm/mm/init.c
arch/arm/mm/mm.h
arch/arm/mm/mmu.c
arch/arm/mm/vmregion.h
arch/x86/Kconfig
arch/x86/include/asm/dma-contiguous.h [new file with mode: 0644]
arch/x86/include/asm/dma-mapping.h
arch/x86/kernel/pci-dma.c
arch/x86/kernel/pci-nommu.c
arch/x86/kernel/setup.c
drivers/base/Kconfig
drivers/base/Makefile
drivers/base/dma-buf.c
drivers/base/dma-coherent.c
drivers/base/dma-contiguous.c [new file with mode: 0644]
drivers/base/dma-mapping.c
drivers/iommu/Kconfig
drivers/iommu/Makefile
drivers/iommu/exynos-iommu.c [new file with mode: 0644]
drivers/media/video/Kconfig
drivers/media/video/s5p-fimc/fimc-capture.c
drivers/media/video/s5p-mfc/s5p_mfc_dec.c
drivers/media/video/s5p-mfc/s5p_mfc_enc.c
drivers/media/video/s5p-tv/Kconfig
drivers/media/video/s5p-tv/mixer_video.c
drivers/media/video/v4l2-compat-ioctl32.c
drivers/media/video/v4l2-dev.c
drivers/media/video/v4l2-ioctl.c
drivers/media/video/v4l2-mem2mem.c
drivers/media/video/videobuf-core.c
drivers/media/video/videobuf2-core.c
drivers/media/video/videobuf2-dma-contig.c
drivers/media/video/videobuf2-memops.c
drivers/media/video/videobuf2-vmalloc.c
drivers/media/video/vivi.c
include/asm-generic/dma-coherent.h
include/asm-generic/dma-contiguous.h [new file with mode: 0644]
include/asm-generic/dma-mapping-common.h
include/linux/device.h
include/linux/dma-attrs.h
include/linux/dma-buf.h
include/linux/dma-contiguous.h [new file with mode: 0644]
include/linux/dma-mapping.h
include/linux/gfp.h
include/linux/mmzone.h
include/linux/page-isolation.h
include/linux/scatterlist.h
include/linux/videodev2.h
include/linux/vmalloc.h
include/media/v4l2-dev.h
include/media/v4l2-ioctl.h
include/media/v4l2-mem2mem.h
include/media/videobuf2-core.h
include/media/videobuf2-memops.h
lib/scatterlist.c
mm/Kconfig
mm/Makefile
mm/compaction.c
mm/internal.h
mm/memory-failure.c
mm/memory_hotplug.c
mm/page_alloc.c
mm/page_isolation.c
mm/vmalloc.c
mm/vmstat.c

index 5c72eed..f503090 100644 (file)
@@ -49,3 +49,45 @@ DMA_ATTR_NON_CONSISTENT lets the platform to choose to return either
 consistent or non-consistent memory as it sees fit.  By using this API,
 you are guaranteeing to the platform that you have all the correct and
 necessary sync points for this memory in the driver.
+
+DMA_ATTR_NO_KERNEL_MAPPING
+--------------------------
+
+DMA_ATTR_NO_KERNEL_MAPPING lets the platform to avoid creating a kernel
+virtual mapping for the allocated buffer. On some architectures creating
+such mapping is non-trivial task and consumes very limited resources
+(like kernel virtual address space or dma consistent address space).
+Buffers allocated with this attribute can be only passed to user space
+by calling dma_mmap_attrs(). By using this API, you are guaranteeing
+that you won't dereference the pointer returned by dma_alloc_attr(). You
+can threat it as a cookie that must be passed to dma_mmap_attrs() and
+dma_free_attrs(). Make sure that both of these also get this attribute
+set on each call.
+
+Since it is optional for platforms to implement
+DMA_ATTR_NO_KERNEL_MAPPING, those that do not will simply ignore the
+attribute and exhibit default behavior.
+
+DMA_ATTR_SKIP_CPU_SYNC
+----------------------
+
+By default dma_map_{single,page,sg} functions family transfer a given
+buffer from CPU domain to device domain. Some advanced use cases might
+require sharing a buffer between more than one device. This requires
+having a mapping created separately for each device and is usually
+performed by calling dma_map_{single,page,sg} function more than once
+for the given buffer with device pointer to each device taking part in
+the buffer sharing. The first call transfers a buffer from 'CPU' domain
+to 'device' domain, what synchronizes CPU caches for the given region
+(usually it means that the cache has been flushed or invalidated
+depending on the dma direction). However, next calls to
+dma_map_{single,page,sg}() for other devices will perform exactly the
+same sychronization operation on the CPU cache. CPU cache sychronization
+might be a time consuming operation, especially if the buffers are
+large, so it is highly recommended to avoid it if possible.
+DMA_ATTR_SKIP_CPU_SYNC allows platform code to skip synchronization of
+the CPU cache for the given buffer assuming that it has been already
+transferred to 'device' domain. This attribute can be also used for
+dma_unmap_{single,page,sg} functions family to force buffer to stay in
+device domain after releasing a mapping for it. Use this attribute with
+care!
index bce97c5..f982371 100644 (file)
@@ -2523,6 +2523,13 @@ ioctls.</para>
         <listitem>
          <para>Selection API. <xref linkend="selection-api" /></para>
         </listitem>
+        <listitem>
+         <para>Importing DMABUF file descriptors as a new IO method described
+         in <xref linkend="dmabuf" />.</para>
+        </listitem>
+        <listitem>
+         <para>Exporting DMABUF files using &VIDIOC-EXPBUF; ioctl.</para>
+        </listitem>
       </itemizedlist>
     </section>
 
index b815929..d9980e0 100644 (file)
@@ -472,6 +472,165 @@ rest should be evident.</para>
       </footnote></para>
   </section>
 
+  <section id="dmabuf">
+    <title>Streaming I/O (DMA buffer importing)</title>
+
+    <note>
+      <title>Experimental</title>
+      <para>This is an <link linkend="experimental"> experimental </link>
+      interface and may change in the future.</para>
+    </note>
+
+<para>The DMABUF framework provides a generic mean for sharing buffers between
+ multiple devices. Device drivers that support DMABUF can export a DMA buffer
+to userspace as a file descriptor (known as the exporter role), import a DMA
+buffer from userspace using a file descriptor previously exported for a
+different or the same device (known as the importer role), or both. This
+section describes the DMABUF importer role API in V4L2.</para>
+
+    <para>Refer to <link linked="vidioc-expbuf"> DMABUF exporting </link> for
+details about exporting a V4L2 buffers as DMABUF file descriptors.</para>
+
+<para>Input and output devices support the streaming I/O method when the
+<constant>V4L2_CAP_STREAMING</constant> flag in the
+<structfield>capabilities</structfield> field of &v4l2-capability; returned by
+the &VIDIOC-QUERYCAP; ioctl is set. Whether importing DMA buffers through
+DMABUF file descriptors is supported is determined by calling the
+&VIDIOC-REQBUFS; ioctl with the memory type set to
+<constant>V4L2_MEMORY_DMABUF</constant>.</para>
+
+    <para>This I/O method is dedicated for sharing DMA buffers between V4L and
+other APIs.  Buffers (planes) are allocated by a driver on behalf of the
+application, and exported to the application as file descriptors using an API
+specific to the allocator driver.  Only those file descriptor are exchanged,
+these files and meta-information are passed in &v4l2-buffer; (or in
+&v4l2-plane; in the multi-planar API case).  The driver must be switched into
+DMABUF I/O mode by calling the &VIDIOC-REQBUFS; with the desired buffer type.
+No buffers (planes) are allocated beforehand, consequently they are not indexed
+and cannot be queried like mapped buffers with the
+<constant>VIDIOC_QUERYBUF</constant> ioctl.</para>
+
+    <example>
+      <title>Initiating streaming I/O with DMABUF file descriptors</title>
+
+      <programlisting>
+&v4l2-requestbuffers; reqbuf;
+
+memset (&amp;reqbuf, 0, sizeof (reqbuf));
+reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+reqbuf.memory = V4L2_MEMORY_DMABUF;
+
+if (ioctl (fd, &VIDIOC-REQBUFS;, &amp;reqbuf) == -1) {
+       if (errno == EINVAL)
+               printf ("Video capturing or DMABUF streaming is not supported\n");
+       else
+               perror ("VIDIOC_REQBUFS");
+
+       exit (EXIT_FAILURE);
+}
+      </programlisting>
+    </example>
+
+    <para>Buffer (plane) file is passed on the fly with the &VIDIOC-QBUF;
+ioctl. In case of multiplanar buffers, every plane can be associated with a
+different DMABUF descriptor.Although buffers are commonly cycled, applications
+can pass different DMABUF descriptor at each <constant>VIDIOC_QBUF</constant>
+call.</para>
+
+    <example>
+      <title>Queueing DMABUF using single plane API</title>
+
+      <programlisting>
+int buffer_queue(int v4lfd, int index, int dmafd)
+{
+       &v4l2-buffer; buf;
+
+       memset(&amp;buf, 0, sizeof buf);
+       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       buf.memory = V4L2_MEMORY_DMABUF;
+       buf.index = index;
+       buf.m.fd = dmafd;
+
+       if (ioctl (v4lfd, &VIDIOC-QBUF;, &amp;buf) == -1) {
+               perror ("VIDIOC_QBUF");
+               return -1;
+       }
+
+       return 0;
+}
+      </programlisting>
+    </example>
+
+    <example>
+      <title>Queueing DMABUF using multi plane API</title>
+
+      <programlisting>
+int buffer_queue_mp(int v4lfd, int index, int dmafd[], int n_planes)
+{
+       &v4l2-buffer; buf;
+       &v4l2-plane; planes[VIDEO_MAX_PLANES];
+       int i;
+
+       memset(&amp;buf, 0, sizeof buf);
+       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+       buf.memory = V4L2_MEMORY_DMABUF;
+       buf.index = index;
+       buf.m.planes = planes;
+       buf.length = n_planes;
+
+       memset(&amp;planes, 0, sizeof planes);
+
+       for (i = 0; i &lt; n_planes; ++i)
+               buf.m.planes[i].m.fd = dmafd[i];
+
+       if (ioctl (v4lfd, &VIDIOC-QBUF;, &amp;buf) == -1) {
+               perror ("VIDIOC_QBUF");
+               return -1;
+       }
+
+       return 0;
+}
+      </programlisting>
+    </example>
+
+    <para>Filled or displayed buffers are dequeued with the
+&VIDIOC-DQBUF; ioctl. The driver can unlock the buffer at any
+time between the completion of the DMA and this ioctl. The memory is
+also unlocked when &VIDIOC-STREAMOFF; is called, &VIDIOC-REQBUFS;, or
+when the device is closed.</para>
+
+    <para>For capturing applications it is customary to enqueue a
+number of empty buffers, to start capturing and enter the read loop.
+Here the application waits until a filled buffer can be dequeued, and
+re-enqueues the buffer when the data is no longer needed. Output
+applications fill and enqueue buffers, when enough buffers are stacked
+up output is started. In the write loop, when the application
+runs out of free buffers it must wait until an empty buffer can be
+dequeued and reused. Two methods exist to suspend execution of the
+application until one or more buffers can be dequeued. By default
+<constant>VIDIOC_DQBUF</constant> blocks when no buffer is in the
+outgoing queue. When the <constant>O_NONBLOCK</constant> flag was
+given to the &func-open; function, <constant>VIDIOC_DQBUF</constant>
+returns immediately with an &EAGAIN; when no buffer is available. The
+&func-select; or &func-poll; function are always available.</para>
+
+    <para>To start and stop capturing or output applications call the
+&VIDIOC-STREAMON; and &VIDIOC-STREAMOFF; ioctls. Note that
+<constant>VIDIOC_STREAMOFF</constant> removes all buffers from both queues and
+unlocks all buffers as a side effect. Since there is no notion of doing
+anything "now" on a multitasking system, if an application needs to synchronize
+with another event it should examine the &v4l2-buffer;
+<structfield>timestamp</structfield> of captured buffers, or set the field
+before enqueuing buffers for output.</para>
+
+    <para>Drivers implementing DMABUF importing I/O must support the
+<constant>VIDIOC_REQBUFS</constant>, <constant>VIDIOC_QBUF</constant>,
+<constant>VIDIOC_DQBUF</constant>, <constant>VIDIOC_STREAMON</constant> and
+<constant>VIDIOC_STREAMOFF</constant> ioctl, the <function>select()</function>
+and <function>poll()</function> function.</para>
+
+  </section>
+
   <section id="async">
     <title>Asynchronous I/O</title>
 
@@ -670,6 +829,14 @@ memory, set by the application. See <xref linkend="userp" /> for details.
            in the <structfield>length</structfield> field of this
            <structname>v4l2_buffer</structname> structure.</entry>
          </row>
+         <row>
+           <entry></entry>
+           <entry>int</entry>
+           <entry><structfield>fd</structfield></entry>
+           <entry>For the single-plane API and when
+<structfield>memory</structfield> is <constant>V4L2_MEMORY_DMABUF</constant> this
+is the file descriptor associated with a DMABUF buffer.</entry>
+         </row>
          <row>
            <entry>__u32</entry>
            <entry><structfield>length</structfield></entry>
@@ -745,6 +912,15 @@ should set this to 0.</entry>
              pointer to the memory allocated for this plane by an application.
              </entry>
          </row>
+         <row>
+           <entry></entry>
+           <entry>int</entry>
+           <entry><structfield>fd</structfield></entry>
+           <entry>When the memory type in the containing &v4l2-buffer; is
+               <constant>V4L2_MEMORY_DMABUF</constant>, this is a file
+               descriptor associated with a DMABUF buffer, similar to the
+               <structfield>fd</structfield> field in &v4l2-buffer;.</entry>
+         </row>
          <row>
            <entry>__u32</entry>
            <entry><structfield>data_offset</structfield></entry>
@@ -980,6 +1156,12 @@ pointer</link> I/O.</entry>
            <entry>3</entry>
            <entry>[to do]</entry>
          </row>
+         <row>
+           <entry><constant>V4L2_MEMORY_DMABUF</constant></entry>
+           <entry>4</entry>
+           <entry>The buffer is used for <link linkend="dmabuf">DMA shared
+buffer</link> I/O.</entry>
+         </row>
        </tbody>
       </tgroup>
     </table>
index 8ae3887..2edb7d5 100644 (file)
@@ -525,6 +525,7 @@ and discussions on the V4L mailing list.</revremark>
     &sub-log-status;
     &sub-overlay;
     &sub-qbuf;
+    &sub-expbuf;
     &sub-querybuf;
     &sub-querycap;
     &sub-queryctrl;
index 73ae8a6..e0f8a20 100644 (file)
@@ -97,8 +97,10 @@ information.</para>
            <entry>&v4l2-memory;</entry>
            <entry><structfield>memory</structfield></entry>
            <entry>Applications set this field to
-<constant>V4L2_MEMORY_MMAP</constant> or
-<constant>V4L2_MEMORY_USERPTR</constant>.</entry>
+<constant>V4L2_MEMORY_MMAP</constant>,
+<constant>V4L2_MEMORY_DMABUF</constant> or
+<constant>V4L2_MEMORY_USERPTR</constant>. See <xref linkend="v4l2-memory"
+/></entry>
          </row>
          <row>
            <entry>&v4l2-format;</entry>
diff --git a/Documentation/DocBook/media/v4l/vidioc-expbuf.xml b/Documentation/DocBook/media/v4l/vidioc-expbuf.xml
new file mode 100644 (file)
index 0000000..30ebf67
--- /dev/null
@@ -0,0 +1,223 @@
+<refentry id="vidioc-expbuf">
+
+  <refmeta>
+    <refentrytitle>ioctl VIDIOC_EXPBUF</refentrytitle>
+    &manvol;
+  </refmeta>
+
+  <refnamediv>
+    <refname>VIDIOC_EXPBUF</refname>
+    <refpurpose>Export a buffer as a DMABUF file descriptor.</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <funcsynopsis>
+      <funcprototype>
+       <funcdef>int <function>ioctl</function></funcdef>
+       <paramdef>int <parameter>fd</parameter></paramdef>
+       <paramdef>int <parameter>request</parameter></paramdef>
+       <paramdef>struct v4l2_exportbuffer *<parameter>argp</parameter></paramdef>
+      </funcprototype>
+    </funcsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Arguments</title>
+
+    <variablelist>
+      <varlistentry>
+       <term><parameter>fd</parameter></term>
+       <listitem>
+         <para>&fd;</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><parameter>request</parameter></term>
+       <listitem>
+         <para>VIDIOC_EXPBUF</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><parameter>argp</parameter></term>
+       <listitem>
+         <para></para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Description</title>
+
+    <note>
+      <title>Experimental</title>
+      <para>This is an <link linkend="experimental"> experimental </link>
+      interface and may change in the future.</para>
+    </note>
+
+<para>This ioctl is an extension to the <link linkend="mmap">memory
+mapping</link> I/O method therefore it is available only for
+<constant>V4L2_MEMORY_MMAP</constant> buffers.  It can be used to export a
+buffer as DMABUF file at any time after buffers have been allocated with the
+&VIDIOC-REQBUFS; ioctl.</para>
+
+<para>Prior to exporting an application calls <link
+linkend="vidioc-querybuf">VIDIOC_QUERYBUF</link> to obtain memory offsets. When
+using the <link linkend="planar-apis">multi-planar API</link> every plane has
+own offset.</para>
+
+<para>To export a buffer, the application fills &v4l2-exportbuffer;.  The
+<structfield> mem_offset </structfield> field is set to the offset obtained
+from <constant> VIDIOC_QUERYBUF </constant>.  Additional flags may be posted in
+the <structfield> flags </structfield> field.  Refer to manual for open syscall
+for details. Currently only O_CLOEXEC is guaranteed to be supported.  All other
+fields must be set to zero.  In a case of multi-planar API, every plane is
+exported separately using multiple <constant> VIDIOC_EXPBUF </constant>
+calls.</para>
+
+<para> After calling <constant>VIDIOC_EXPBUF</constant> the <structfield> fd
+</structfield> field will be set by a driver.  This is a DMABUF file
+descriptor. The application may pass it to other API. Refer to <link
+linkend="dmabuf">DMABUF importing</link> for details about importing DMABUF
+files into V4L2 nodes. A developer is encouraged to close a DMABUF file when it
+is no longer used.  </para>
+
+  </refsect1>
+  <refsect1>
+   <section>
+      <title>Examples</title>
+
+      <example>
+       <title>Exporting a buffer.</title>
+       <programlisting>
+int buffer_export(int v4lfd, &v4l2-buf-type; bt, int index, int *dmafd)
+{
+       &v4l2-buffer; buf;
+       &v4l2-exportbuffer; expbuf;
+
+       memset(&amp;buf, 0, sizeof buf);
+       buf.type = bt;
+       buf.memory = V4L2_MEMORY_MMAP;
+       buf.index = index;
+
+       if (ioctl (v4lfd, &VIDIOC-QUERYBUF;, &amp;buf) == -1) {
+               perror ("VIDIOC_QUERYBUF");
+               return -1;
+       }
+
+       memset(&amp;expbuf, 0, sizeof expbuf);
+       expbuf.mem_offset = buf.m.offset;
+       if (ioctl (v4lfd, &VIDIOC-EXPBUF;, &amp;expbuf) == -1) {
+               perror ("VIDIOC_EXPBUF");
+               return -1;
+       }
+
+       *dmafd = expbuf.fd;
+
+       return 0;
+}
+        </programlisting>
+      </example>
+
+      <example>
+       <title>Exporting a buffer using multi plane API.</title>
+       <programlisting>
+int buffer_export_mp(int v4lfd, &v4l2-buf-type; bt, int index,
+       int dmafd[], int n_planes)
+{
+       &v4l2-buffer; buf;
+       &v4l2-plane; planes[VIDEO_MAX_PLANES];
+       int i;
+
+       memset(&amp;buf, 0, sizeof buf);
+       buf.type = bt;
+       buf.memory = V4L2_MEMORY_MMAP;
+       buf.index = index;
+       buf.m.planes = planes;
+       buf.length = n_planes;
+       memset(&amp;planes, 0, sizeof planes);
+
+       if (ioctl (v4lfd, &VIDIOC-QUERYBUF;, &amp;buf) == -1) {
+               perror ("VIDIOC_QUERYBUF");
+               return -1;
+       }
+
+       for (i = 0; i &lt; n_planes; ++i) {
+               &v4l2-exportbuffer; expbuf;
+
+               memset(&amp;expbuf, 0, sizeof expbuf);
+               expbuf.mem_offset = plane[i].m.offset;
+               if (ioctl (v4lfd, &VIDIOC-EXPBUF;, &amp;expbuf) == -1) {
+                       perror ("VIDIOC_EXPBUF");
+                       while (i)
+                               close(dmafd[--i]);
+                       return -1;
+               }
+               dmafd[i] = expbuf.fd;
+       }
+
+       return 0;
+}
+        </programlisting>
+      </example>
+   </section>
+  </refsect1>
+
+  <refsect1>
+    <table pgwide="1" frame="none" id="v4l2-exportbuffer">
+      <title>struct <structname>v4l2_exportbuffer</structname></title>
+      <tgroup cols="3">
+       &cs-str;
+       <tbody valign="top">
+         <row>
+           <entry>__u32</entry>
+           <entry><structfield>fd</structfield></entry>
+           <entry>The DMABUF file descriptor associated with a buffer. Set by
+               a driver.</entry>
+         </row>
+         <row>
+           <entry>__u32</entry>
+           <entry><structfield>reserved0</structfield></entry>
+           <entry>Reserved field for future use. Must be set to zero.</entry>
+         </row>
+         <row>
+           <entry>__u32</entry>
+           <entry><structfield>mem_offset</structfield></entry>
+           <entry>Buffer memory offset as returned by <constant>
+VIDIOC_QUERYBUF </constant> in &v4l2-buffer;<structfield> ::m.offset
+</structfield> (for single-plane formats) or &v4l2-plane;<structfield>
+::m.offset </structfield> (for multi-planar formats)</entry>
+         </row>
+         <row>
+           <entry>__u32</entry>
+           <entry><structfield>flags</structfield></entry>
+           <entry>Flags for newly created file, currently only <constant>
+O_CLOEXEC </constant> is supported, refer to manual of open syscall for more
+details.</entry>
+         </row>
+         <row>
+           <entry>__u32</entry>
+           <entry><structfield>reserved[12]</structfield></entry>
+           <entry>Reserved field for future use. Must be set to zero.</entry>
+         </row>
+       </tbody>
+      </tgroup>
+    </table>
+
+  </refsect1>
+
+  <refsect1>
+    &return-value;
+    <variablelist>
+      <varlistentry>
+       <term><errorcode>EINVAL</errorcode></term>
+       <listitem>
+         <para>A queue is not in MMAP mode or DMABUF exporting is not
+supported or <structfield> flag </structfield> or <structfield> mem_offset
+</structfield> fields are invalid.</para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+</refentry>
index 9caa49a..cb5f5ff 100644 (file)
@@ -112,6 +112,21 @@ they cannot be swapped out to disk. Buffers remain locked until
 dequeued, until the &VIDIOC-STREAMOFF; or &VIDIOC-REQBUFS; ioctl is
 called, or until the device is closed.</para>
 
+    <para>To enqueue a <link linkend="dmabuf">DMABUF</link> buffer applications
+set the <structfield>memory</structfield> field to
+<constant>V4L2_MEMORY_DMABUF</constant> and the <structfield>m.fd</structfield>
+to a file descriptor associated with a DMABUF buffer. When the multi-planar API is
+used and <structfield>m.fd</structfield> of the passed array of &v4l2-plane;
+have to be used instead. When <constant>VIDIOC_QBUF</constant> is called with a
+pointer to this structure the driver sets the
+<constant>V4L2_BUF_FLAG_QUEUED</constant> flag and clears the
+<constant>V4L2_BUF_FLAG_MAPPED</constant> and
+<constant>V4L2_BUF_FLAG_DONE</constant> flags in the
+<structfield>flags</structfield> field, or it returns an error code.  This
+ioctl locks the buffer. Buffers remain locked until dequeued,
+until the &VIDIOC-STREAMOFF; or &VIDIOC-REQBUFS; ioctl is called, or until the
+device is closed.</para>
+
     <para>Applications call the <constant>VIDIOC_DQBUF</constant>
 ioctl to dequeue a filled (capturing) or displayed (output) buffer
 from the driver's outgoing queue. They just set the
index 7be4b1d..7911044 100644 (file)
   <refsect1>
     <title>Description</title>
 
-    <para>This ioctl is used to initiate <link linkend="mmap">memory
-mapped</link> or <link linkend="userp">user pointer</link>
-I/O. Memory mapped buffers are located in device memory and must be
-allocated with this ioctl before they can be mapped into the
-application's address space. User buffers are allocated by
-applications themselves, and this ioctl is merely used to switch the
-driver into user pointer I/O mode and to setup some internal structures.</para>
+<para>This ioctl is used to initiate <link linkend="mmap">memory mapped</link>,
+<link linkend="userp">user pointer</link> or <link
+linkend="dmabuf">DMABUF</link> based I/O.  Memory mapped buffers are located in
+device memory and must be allocated with this ioctl before they can be mapped
+into the application's address space. User buffers are allocated by
+applications themselves, and this ioctl is merely used to switch the driver
+into user pointer I/O mode and to setup some internal structures.
+Similarly, DMABUF buffers are allocated by applications through a device
+driver, and this ioctl only configures the driver into DMABUF I/O mode without
+performing any direct allocation.</para>
 
-    <para>To allocate device buffers applications initialize all
-fields of the <structname>v4l2_requestbuffers</structname> structure.
-They set the <structfield>type</structfield> field to the respective
-stream or buffer type, the <structfield>count</structfield> field to
-the desired number of buffers, <structfield>memory</structfield>
-must be set to the requested I/O method and the <structfield>reserved</structfield> array
-must be zeroed. When the ioctl
-is called with a pointer to this structure the driver will attempt to allocate
-the requested number of buffers and it stores the actual number
-allocated in the <structfield>count</structfield> field. It can be
-smaller than the number requested, even zero, when the driver runs out
-of free memory. A larger number is also possible when the driver requires
-more buffers to function correctly. For example video output requires at least two buffers,
-one displayed and one filled by the application.</para>
+    <para>To allocate device buffers applications initialize all fields of the
+<structname>v4l2_requestbuffers</structname> structure.  They set the
+<structfield>type</structfield> field to the respective stream or buffer type,
+the <structfield>count</structfield> field to the desired number of buffers,
+<structfield>memory</structfield> must be set to the requested I/O method and
+the <structfield>reserved</structfield> array must be zeroed. When the ioctl is
+called with a pointer to this structure the driver will attempt to allocate the
+requested number of buffers and it stores the actual number allocated in the
+<structfield>count</structfield> field. It can be smaller than the number
+requested, even zero, when the driver runs out of free memory. A larger number
+is also possible when the driver requires more buffers to function correctly.
+For example video output requires at least two buffers, one displayed and one
+filled by the application.</para>
     <para>When the I/O method is not supported the ioctl
 returns an &EINVAL;.</para>
 
@@ -102,8 +104,10 @@ as the &v4l2-format; <structfield>type</structfield> field. See <xref
            <entry>&v4l2-memory;</entry>
            <entry><structfield>memory</structfield></entry>
            <entry>Applications set this field to
-<constant>V4L2_MEMORY_MMAP</constant> or
-<constant>V4L2_MEMORY_USERPTR</constant>.</entry>
+<constant>V4L2_MEMORY_MMAP</constant>,
+<constant>V4L2_MEMORY_DMABUF</constant> or
+<constant>V4L2_MEMORY_USERPTR</constant>. See <xref linkend="v4l2-memory"
+/>.</entry>
          </row>
          <row>
            <entry>__u32</entry>
index 3bbd5c5..ad86fb8 100644 (file)
@@ -29,13 +29,6 @@ The buffer-user
    in memory, mapped into its own address space, so it can access the same area
    of memory.
 
-*IMPORTANT*: [see https://lkml.org/lkml/2011/12/20/211 for more details]
-For this first version, A buffer shared using the dma_buf sharing API:
-- *may* be exported to user space using "mmap" *ONLY* by exporter, outside of
-  this framework.
-- with this new iteration of the dma-buf api cpu access from the kernel has been
-  enable, see below for the details.
-
 dma-buf operations for device dma only
 --------------------------------------
 
@@ -300,6 +293,17 @@ Access to a dma_buf from the kernel context involves three steps:
    Note that these calls need to always succeed. The exporter needs to complete
    any preparations that might fail in begin_cpu_access.
 
+   For some cases the overhead of kmap can be too high, a vmap interface
+   is introduced. This interface should be used very carefully, as vmalloc
+   space is a limited resources on many architectures.
+
+   Interfaces:
+      void *dma_buf_vmap(struct dma_buf *dmabuf)
+      void dma_buf_vunmap(struct dma_buf *dmabuf, void *vaddr)
+
+   The vmap call can fail if there is no vmap support in the exporter, or if it
+   runs out of vmalloc space. Fallback to kmap should be implemented.
+
 3. Finish access
 
    When the importer is done accessing the range specified in begin_cpu_access,
@@ -313,6 +317,83 @@ Access to a dma_buf from the kernel context involves three steps:
                                  enum dma_data_direction dir);
 
 
+Direct Userspace Access/mmap Support
+------------------------------------
+
+Being able to mmap an export dma-buf buffer object has 2 main use-cases:
+- CPU fallback processing in a pipeline and
+- supporting existing mmap interfaces in importers.
+
+1. CPU fallback processing in a pipeline
+
+   In many processing pipelines it is sometimes required that the cpu can access
+   the data in a dma-buf (e.g. for thumbnail creation, snapshots, ...). To avoid
+   the need to handle this specially in userspace frameworks for buffer sharing
+   it's ideal if the dma_buf fd itself can be used to access the backing storage
+   from userspace using mmap.
+
+   Furthermore Android's ION framework already supports this (and is otherwise
+   rather similar to dma-buf from a userspace consumer side with using fds as
+   handles, too). So it's beneficial to support this in a similar fashion on
+   dma-buf to have a good transition path for existing Android userspace.
+
+   No special interfaces, userspace simply calls mmap on the dma-buf fd.
+
+2. Supporting existing mmap interfaces in exporters
+
+   Similar to the motivation for kernel cpu access it is again important that
+   the userspace code of a given importing subsystem can use the same interfaces
+   with a imported dma-buf buffer object as with a native buffer object. This is
+   especially important for drm where the userspace part of contemporary OpenGL,
+   X, and other drivers is huge, and reworking them to use a different way to
+   mmap a buffer rather invasive.
+
+   The assumption in the current dma-buf interfaces is that redirecting the
+   initial mmap is all that's needed. A survey of some of the existing
+   subsystems shows that no driver seems to do any nefarious thing like syncing
+   up with outstanding asynchronous processing on the device or allocating
+   special resources at fault time. So hopefully this is good enough, since
+   adding interfaces to intercept pagefaults and allow pte shootdowns would
+   increase the complexity quite a bit.
+
+   Interface:
+      int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *,
+                      unsigned long);
+
+   If the importing subsystem simply provides a special-purpose mmap call to set
+   up a mapping in userspace, calling do_mmap with dma_buf->file will equally
+   achieve that for a dma-buf object.
+
+3. Implementation notes for exporters
+
+   Because dma-buf buffers have invariant size over their lifetime, the dma-buf
+   core checks whether a vma is too large and rejects such mappings. The
+   exporter hence does not need to duplicate this check.
+
+   Because existing importing subsystems might presume coherent mappings for
+   userspace, the exporter needs to set up a coherent mapping. If that's not
+   possible, it needs to fake coherency by manually shooting down ptes when
+   leaving the cpu domain and flushing caches at fault time. Note that all the
+   dma_buf files share the same anon inode, hence the exporter needs to replace
+   the dma_buf file stored in vma->vm_file with it's own if pte shootdown is
+   requred. This is because the kernel uses the underlying inode's address_space
+   for vma tracking (and hence pte tracking at shootdown time with
+   unmap_mapping_range).
+
+   If the above shootdown dance turns out to be too expensive in certain
+   scenarios, we can extend dma-buf with a more explicit cache tracking scheme
+   for userspace mappings. But the current assumption is that using mmap is
+   always a slower path, so some inefficiencies should be acceptable.
+
+   Exporters that shoot down mappings (for any reasons) shall not do any
+   synchronization at fault time with outstanding device operations.
+   Synchronization is an orthogonal issue to sharing the backing storage of a
+   buffer and hence should not be handled by dma-buf itself. This is explictly
+   mentioned here because many people seem to want something like this, but if
+   different exporters handle this differently, buffer sharing can fail in
+   interesting ways depending upong the exporter (if userspace starts depending
+   upon this implicit synchronization).
+
 Miscellaneous notes
 -------------------
 
@@ -336,6 +417,20 @@ Miscellaneous notes
   the exporting driver to create a dmabuf fd must provide a way to let
   userspace control setting of O_CLOEXEC flag passed in to dma_buf_fd().
 
+- If an exporter needs to manually flush caches and hence needs to fake
+  coherency for mmap support, it needs to be able to zap all the ptes pointing
+  at the backing storage. Now linux mm needs a struct address_space associated
+  with the struct file stored in vma->vm_file to do that with the function
+  unmap_mapping_range. But the dma_buf framework only backs every dma_buf fd
+  with the anon_file struct file, i.e. all dma_bufs share the same file.
+
+  Hence exporters need to setup their own file (and address_space) association
+  by setting vma->vm_file and adjusting vma->vm_pgoff in the dma_buf mmap
+  callback. In the specific case of a gem driver the exporter could use the
+  shmem file already provided by gem (and set vm_pgoff = 0). Exporters can then
+  zap ptes by unmapping the corresponding range of the struct address_space
+  associated with their own file.
+
 References:
 [1] struct dma_buf_ops in include/linux/dma-buf.h
 [2] All interfaces mentioned above defined in include/linux/dma-buf.h
index c1601e5..3cfaca1 100644 (file)
@@ -508,6 +508,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
                        Also note the kernel might malfunction if you disable
                        some critical bits.
 
+       cma=nn[MG]      [ARM,KNL]
+                       Sets the size of kernel global memory area for contiguous
+                       memory allocations. For more information, see
+                       include/linux/dma-contiguous.h
+
        cmo_free_hint=  [PPC] Format: { yes | no }
                        Specify whether pages are marked as being inactive
                        when they are freed.  This is used in CMO environments
@@ -515,6 +520,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
                        a hypervisor.
                        Default: yes
 
+       coherent_pool=nn[KMG]   [ARM,KNL]
+                       Sets the size of memory pool for coherent, atomic dma
+                       allocations.
+
        code_bytes      [X86] How many bytes of object code to print
                        in an oops report.
                        Range: 0 - 8192
index 659b2ba..320dabb 100644 (file)
@@ -555,19 +555,25 @@ allocated memory.
 You should also set these fields:
 
 - v4l2_dev: set to the v4l2_device parent device.
+
 - name: set to something descriptive and unique.
+
 - fops: set to the v4l2_file_operations struct.
+
 - ioctl_ops: if you use the v4l2_ioctl_ops to simplify ioctl maintenance
   (highly recommended to use this and it might become compulsory in the
   future!), then set this to your v4l2_ioctl_ops struct.
+
 - lock: leave to NULL if you want to do all the locking in the driver.
   Otherwise you give it a pointer to a struct mutex_lock and before any
   of the v4l2_file_operations is called this lock will be taken by the
-  core and released afterwards.
+  core and released afterwards. See the next section for more details.
+
 - prio: keeps track of the priorities. Used to implement VIDIOC_G/S_PRIORITY.
   If left to NULL, then it will use the struct v4l2_prio_state in v4l2_device.
   If you want to have a separate priority state per (group of) device node(s),
   then you can point it to your own struct v4l2_prio_state.
+
 - parent: you only set this if v4l2_device was registered with NULL as
   the parent device struct. This only happens in cases where one hardware
   device has multiple PCI devices that all share the same v4l2_device core.
@@ -577,6 +583,7 @@ You should also set these fields:
   (cx8802). Since the v4l2_device cannot be associated with a particular
   PCI device it is setup without a parent device. But when the struct
   video_device is setup you do know which parent PCI device to use.
+
 - flags: optional. Set to V4L2_FL_USE_FH_PRIO if you want to let the framework
   handle the VIDIOC_G/S_PRIORITY ioctls. This requires that you use struct
   v4l2_fh. Eventually this flag will disappear once all drivers use the core
@@ -609,8 +616,22 @@ v4l2_file_operations and locking
 --------------------------------
 
 You can set a pointer to a mutex_lock in struct video_device. Usually this
-will be either a top-level mutex or a mutex per device node. If you want
-finer-grained locking then you have to set it to NULL and do you own locking.
+will be either a top-level mutex or a mutex per device node. By default this
+lock will be used for each file operation and ioctl, but you can disable
+locking for selected ioctls by calling:
+
+       void v4l2_dont_use_lock(struct video_device *vdev, unsigned int cmd);
+
+E.g.: v4l2_dont_use_lock(vdev, VIDIOC_DQBUF);
+
+You have to call this before you register the video_device.
+
+Particularly with USB drivers where certain commands such as setting controls
+can take a long time you may want to do your own locking for the buffer queuing
+ioctls.
+
+If you want still finer-grained locking then you have to set mutex_lock to NULL
+and do you own locking completely.
 
 It is up to the driver developer to decide which method to use. However, if
 your driver has high-latency operations (for example, changing the exposure
index 684eb5a..0a3ffe4 100644 (file)
@@ -142,6 +142,9 @@ config HAVE_ARCH_TRACEHOOK
 config HAVE_DMA_ATTRS
        bool
 
+config HAVE_DMA_CONTIGUOUS
+       bool
+
 config USE_GENERIC_SMP_HELPERS
        bool
 
index 36586db..24d3302 100644 (file)
@@ -4,6 +4,9 @@ config ARM
        select HAVE_AOUT
        select HAVE_DMA_API_DEBUG
        select HAVE_IDE if PCI || ISA || PCMCIA
+       select HAVE_DMA_ATTRS
+       select HAVE_DMA_CONTIGUOUS if (CPU_V6 || CPU_V6K || CPU_V7)
+       select CMA if (CPU_V6 || CPU_V6K || CPU_V7)
        select HAVE_MEMBLOCK
        select RTC_LIB
        select SYS_SUPPORTS_APM_EMULATION
@@ -45,6 +48,14 @@ config ARM
 config ARM_HAS_SG_CHAIN
        bool
 
+config NEED_SG_DMA_LENGTH
+       bool
+
+config ARM_DMA_USE_IOMMU
+       select NEED_SG_DMA_LENGTH
+       select ARM_HAS_SG_CHAIN
+       bool
+
 config HAVE_PWM
        bool
 
index 595ecd2..1143c4d 100644 (file)
@@ -173,7 +173,8 @@ find_safe_buffer(struct dmabounce_device_info *device_info, dma_addr_t safe_dma_
        read_lock_irqsave(&device_info->lock, flags);
 
        list_for_each_entry(b, &device_info->safe_buffers, node)
-               if (b->safe_dma_addr == safe_dma_addr) {
+               if (b->safe_dma_addr <= safe_dma_addr &&
+                   b->safe_dma_addr + b->size > safe_dma_addr) {
                        rb = b;
                        break;
                }
@@ -254,7 +255,7 @@ static inline dma_addr_t map_single(struct device *dev, void *ptr, size_t size,
        if (buf == NULL) {
                dev_err(dev, "%s: unable to map unsafe buffer %p!\n",
                       __func__, ptr);
-               return ~0;
+               return DMA_ERROR_CODE;
        }
 
        dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
@@ -307,8 +308,9 @@ static inline void unmap_single(struct device *dev, struct safe_buffer *buf,
  * substitute the safe buffer for the unsafe one.
  * (basically move the buffer from an unsafe area to a safe one)
  */
-dma_addr_t __dma_map_page(struct device *dev, struct page *page,
-               unsigned long offset, size_t size, enum dma_data_direction dir)
+static dma_addr_t dmabounce_map_page(struct device *dev, struct page *page,
+               unsigned long offset, size_t size, enum dma_data_direction dir,
+               struct dma_attrs *attrs)
 {
        dma_addr_t dma_addr;
        int ret;
@@ -320,21 +322,20 @@ dma_addr_t __dma_map_page(struct device *dev, struct page *page,
 
        ret = needs_bounce(dev, dma_addr, size);
        if (ret < 0)
-               return ~0;
+               return DMA_ERROR_CODE;
 
        if (ret == 0) {
-               __dma_page_cpu_to_dev(page, offset, size, dir);
+               arm_dma_ops.sync_single_for_device(dev, dma_addr, size, dir);
                return dma_addr;
        }
 
        if (PageHighMem(page)) {
                dev_err(dev, "DMA buffer bouncing of HIGHMEM pages is not supported\n");
-               return ~0;
+               return DMA_ERROR_CODE;
        }
 
        return map_single(dev, page_address(page) + offset, size, dir);
 }
-EXPORT_SYMBOL(__dma_map_page);
 
 /*
  * see if a mapped address was really a "safe" buffer and if so, copy
@@ -342,8 +343,8 @@ EXPORT_SYMBOL(__dma_map_page);
  * the safe buffer.  (basically return things back to the way they
  * should be)
  */
-void __dma_unmap_page(struct device *dev, dma_addr_t dma_addr, size_t size,
-               enum dma_data_direction dir)
+static void dmabounce_unmap_page(struct device *dev, dma_addr_t dma_addr, size_t size,
+               enum dma_data_direction dir, struct dma_attrs *attrs)
 {
        struct safe_buffer *buf;
 
@@ -352,31 +353,32 @@ void __dma_unmap_page(struct device *dev, dma_addr_t dma_addr, size_t size,
 
        buf = find_safe_buffer_dev(dev, dma_addr, __func__);
        if (!buf) {
-               __dma_page_dev_to_cpu(pfn_to_page(dma_to_pfn(dev, dma_addr)),
-                       dma_addr & ~PAGE_MASK, size, dir);
+               arm_dma_ops.sync_single_for_cpu(dev, dma_addr, size, dir);
                return;
        }
 
        unmap_single(dev, buf, size, dir);
 }
-EXPORT_SYMBOL(__dma_unmap_page);
 
-int dmabounce_sync_for_cpu(struct device *dev, dma_addr_t addr,
-               unsigned long off, size_t sz, enum dma_data_direction dir)
+static int __dmabounce_sync_for_cpu(struct device *dev, dma_addr_t addr,
+               size_t sz, enum dma_data_direction dir)
 {
        struct safe_buffer *buf;
+       unsigned long off;
 
-       dev_dbg(dev, "%s(dma=%#x,off=%#lx,sz=%zx,dir=%x)\n",
-               __func__, addr, off, sz, dir);
+       dev_dbg(dev, "%s(dma=%#x,sz=%zx,dir=%x)\n",
+               __func__, addr, sz, dir);
 
        buf = find_safe_buffer_dev(dev, addr, __func__);
        if (!buf)
                return 1;
 
+       off = addr - buf->safe_dma_addr;
+
        BUG_ON(buf->direction != dir);
 
-       dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
-               __func__, buf->ptr, virt_to_dma(dev, buf->ptr),
+       dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x off=%#lx) mapped to %p (dma=%#x)\n",
+               __func__, buf->ptr, virt_to_dma(dev, buf->ptr), off,
                buf->safe, buf->safe_dma_addr);
 
        DO_STATS(dev->archdata.dmabounce->bounce_count++);
@@ -388,24 +390,35 @@ int dmabounce_sync_for_cpu(struct device *dev, dma_addr_t addr,
        }
        return 0;
 }
-EXPORT_SYMBOL(dmabounce_sync_for_cpu);
 
-int dmabounce_sync_for_device(struct device *dev, dma_addr_t addr,
-               unsigned long off, size_t sz, enum dma_data_direction dir)
+static void dmabounce_sync_for_cpu(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       if (!__dmabounce_sync_for_cpu(dev, handle, size, dir))
+               return;
+
+       arm_dma_ops.sync_single_for_cpu(dev, handle, size, dir);
+}
+
+static int __dmabounce_sync_for_device(struct device *dev, dma_addr_t addr,
+               size_t sz, enum dma_data_direction dir)
 {
        struct safe_buffer *buf;
+       unsigned long off;
 
-       dev_dbg(dev, "%s(dma=%#x,off=%#lx,sz=%zx,dir=%x)\n",
-               __func__, addr, off, sz, dir);
+       dev_dbg(dev, "%s(dma=%#x,sz=%zx,dir=%x)\n",
+               __func__, addr, sz, dir);
 
        buf = find_safe_buffer_dev(dev, addr, __func__);
        if (!buf)
                return 1;
 
+       off = addr - buf->safe_dma_addr;
+
        BUG_ON(buf->direction != dir);
 
-       dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x) mapped to %p (dma=%#x)\n",
-               __func__, buf->ptr, virt_to_dma(dev, buf->ptr),
+       dev_dbg(dev, "%s: unsafe buffer %p (dma=%#x off=%#lx) mapped to %p (dma=%#x)\n",
+               __func__, buf->ptr, virt_to_dma(dev, buf->ptr), off,
                buf->safe, buf->safe_dma_addr);
 
        DO_STATS(dev->archdata.dmabounce->bounce_count++);
@@ -417,7 +430,39 @@ int dmabounce_sync_for_device(struct device *dev, dma_addr_t addr,
        }
        return 0;
 }
-EXPORT_SYMBOL(dmabounce_sync_for_device);
+
+static void dmabounce_sync_for_device(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       if (!__dmabounce_sync_for_device(dev, handle, size, dir))
+               return;
+
+       arm_dma_ops.sync_single_for_device(dev, handle, size, dir);
+}
+
+static int dmabounce_set_mask(struct device *dev, u64 dma_mask)
+{
+       if (dev->archdata.dmabounce)
+               return 0;
+
+       return arm_dma_ops.set_dma_mask(dev, dma_mask);
+}
+
+static struct dma_map_ops dmabounce_ops = {
+       .alloc                  = arm_dma_alloc,
+       .free                   = arm_dma_free,
+       .mmap                   = arm_dma_mmap,
+       .get_sgtable            = arm_dma_get_sgtable,
+       .map_page               = dmabounce_map_page,
+       .unmap_page             = dmabounce_unmap_page,
+       .sync_single_for_cpu    = dmabounce_sync_for_cpu,
+       .sync_single_for_device = dmabounce_sync_for_device,
+       .map_sg                 = arm_dma_map_sg,
+       .unmap_sg               = arm_dma_unmap_sg,
+       .sync_sg_for_cpu        = arm_dma_sync_sg_for_cpu,
+       .sync_sg_for_device     = arm_dma_sync_sg_for_device,
+       .set_dma_mask           = dmabounce_set_mask,
+};
 
 static int dmabounce_init_pool(struct dmabounce_pool *pool, struct device *dev,
                const char *name, unsigned long size)
@@ -479,6 +524,7 @@ int dmabounce_register_dev(struct device *dev, unsigned long small_buffer_size,
 #endif
 
        dev->archdata.dmabounce = device_info;
+       set_dma_ops(dev, &dmabounce_ops);
 
        dev_info(dev, "dmabounce: registered device\n");
 
@@ -497,6 +543,7 @@ void dmabounce_unregister_dev(struct device *dev)
        struct dmabounce_device_info *device_info = dev->archdata.dmabounce;
 
        dev->archdata.dmabounce = NULL;
+       set_dma_ops(dev, NULL);
 
        if (!device_info) {
                dev_warn(dev,
index 7aa3680..b69c0d3 100644 (file)
@@ -7,12 +7,16 @@
 #define ASMARM_DEVICE_H
 
 struct dev_archdata {
+       struct dma_map_ops      *dma_ops;
 #ifdef CONFIG_DMABOUNCE
        struct dmabounce_device_info *dmabounce;
 #endif
 #ifdef CONFIG_IOMMU_API
        void *iommu; /* private IOMMU data */
 #endif
+#ifdef CONFIG_ARM_DMA_USE_IOMMU
+       struct dma_iommu_mapping        *mapping;
+#endif
 };
 
 struct omap_device;
diff --git a/arch/arm/include/asm/dma-contiguous.h b/arch/arm/include/asm/dma-contiguous.h
new file mode 100644 (file)
index 0000000..3ed37b4
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef ASMARM_DMA_CONTIGUOUS_H
+#define ASMARM_DMA_CONTIGUOUS_H
+
+#ifdef __KERNEL__
+#ifdef CONFIG_CMA
+
+#include <linux/types.h>
+#include <asm-generic/dma-contiguous.h>
+
+void dma_contiguous_early_fixup(phys_addr_t base, unsigned long size);
+
+#endif
+#endif
+
+#endif
diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h
new file mode 100644 (file)
index 0000000..799b094
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef ASMARM_DMA_IOMMU_H
+#define ASMARM_DMA_IOMMU_H
+
+#ifdef __KERNEL__
+
+#include <linux/mm_types.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-debug.h>
+#include <linux/kmemcheck.h>
+
+struct dma_iommu_mapping {
+       /* iommu specific data */
+       struct iommu_domain     *domain;
+
+       void                    *bitmap;
+       size_t                  bits;
+       unsigned int            order;
+       dma_addr_t              base;
+
+       spinlock_t              lock;
+       struct kref             kref;
+};
+
+struct dma_iommu_mapping *
+arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size,
+                        int order);
+
+void arm_iommu_release_mapping(struct dma_iommu_mapping *mapping);
+
+int arm_iommu_attach_device(struct device *dev,
+                                       struct dma_iommu_mapping *mapping);
+
+#endif /* __KERNEL__ */
+#endif
index cb3b7c9..804bf65 100644 (file)
@@ -5,11 +5,35 @@
 
 #include <linux/mm_types.h>
 #include <linux/scatterlist.h>
+#include <linux/dma-attrs.h>
 #include <linux/dma-debug.h>
 
 #include <asm-generic/dma-coherent.h>
 #include <asm/memory.h>
 
+#define DMA_ERROR_CODE (~0)
+extern struct dma_map_ops arm_dma_ops;
+
+static inline struct dma_map_ops *get_dma_ops(struct device *dev)
+{
+       if (dev && dev->archdata.dma_ops)
+               return dev->archdata.dma_ops;
+       return &arm_dma_ops;
+}
+
+static inline void set_dma_ops(struct device *dev, struct dma_map_ops *ops)
+{
+       BUG_ON(!dev);
+       dev->archdata.dma_ops = ops;
+}
+
+#include <asm-generic/dma-mapping-common.h>
+
+static inline int dma_set_mask(struct device *dev, u64 mask)
+{
+       return get_dma_ops(dev)->set_dma_mask(dev, mask);
+}
+
 #ifdef __arch_page_to_dma
 #error Please update to __arch_pfn_to_dma
 #endif
@@ -61,69 +85,12 @@ static inline dma_addr_t virt_to_dma(struct device *dev, void *addr)
 }
 #endif
 
-/*
- * The DMA API is built upon the notion of "buffer ownership".  A buffer
- * is either exclusively owned by the CPU (and therefore may be accessed
- * by it) or exclusively owned by the DMA device.  These helper functions
- * represent the transitions between these two ownership states.
- *
- * Note, however, that on later ARMs, this notion does not work due to
- * speculative prefetches.  We model our approach on the assumption that
- * the CPU does do speculative prefetches, which means we clean caches
- * before transfers and delay cache invalidation until transfer completion.
- *
- * Private support functions: these are not part of the API and are
- * liable to change.  Drivers must not use these.
- */
-static inline void __dma_single_cpu_to_dev(const void *kaddr, size_t size,
-       enum dma_data_direction dir)
-{
-       extern void ___dma_single_cpu_to_dev(const void *, size_t,
-               enum dma_data_direction);
-
-       if (!arch_is_coherent())
-               ___dma_single_cpu_to_dev(kaddr, size, dir);
-}
-
-static inline void __dma_single_dev_to_cpu(const void *kaddr, size_t size,
-       enum dma_data_direction dir)
-{
-       extern void ___dma_single_dev_to_cpu(const void *, size_t,
-               enum dma_data_direction);
-
-       if (!arch_is_coherent())
-               ___dma_single_dev_to_cpu(kaddr, size, dir);
-}
-
-static inline void __dma_page_cpu_to_dev(struct page *page, unsigned long off,
-       size_t size, enum dma_data_direction dir)
-{
-       extern void ___dma_page_cpu_to_dev(struct page *, unsigned long,
-               size_t, enum dma_data_direction);
-
-       if (!arch_is_coherent())
-               ___dma_page_cpu_to_dev(page, off, size, dir);
-}
-
-static inline void __dma_page_dev_to_cpu(struct page *page, unsigned long off,
-       size_t size, enum dma_data_direction dir)
-{
-       extern void ___dma_page_dev_to_cpu(struct page *, unsigned long,
-               size_t, enum dma_data_direction);
-
-       if (!arch_is_coherent())
-               ___dma_page_dev_to_cpu(page, off, size, dir);
-}
-
-extern int dma_supported(struct device *, u64);
-extern int dma_set_mask(struct device *, u64);
-
 /*
  * DMA errors are defined by all-bits-set in the DMA address.
  */
 static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
 {
-       return dma_addr == ~0;
+       return dma_addr == DMA_ERROR_CODE;
 }
 
 /*
@@ -141,79 +108,126 @@ static inline void dma_free_noncoherent(struct device *dev, size_t size,
 {
 }
 
+extern int dma_supported(struct device *dev, u64 mask);
+
 /**
- * dma_alloc_coherent - allocate consistent memory for DMA
+ * arm_dma_alloc - allocate consistent memory for DMA
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @size: required memory size
  * @handle: bus-specific DMA address
+ * @attrs: optinal attributes that specific mapping properties
  *
- * Allocate some uncached, unbuffered memory for a device for
- * performing DMA.  This function allocates pages, and will
- * return the CPU-viewed address, and sets @handle to be the
- * device-viewed address.
+ * Allocate some memory for a device for performing DMA.  This function
+ * allocates pages, and will return the CPU-viewed address, and sets @handle
+ * to be the device-viewed address.
  */
-extern void *dma_alloc_coherent(struct device *, size_t, dma_addr_t *, gfp_t);
+extern void *arm_dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
+                          gfp_t gfp, struct dma_attrs *attrs);
+
+#define dma_alloc_coherent(d, s, h, f) dma_alloc_attrs(d, s, h, f, NULL)
+
+static inline void *dma_alloc_attrs(struct device *dev, size_t size,
+                                      dma_addr_t *dma_handle, gfp_t flag,
+                                      struct dma_attrs *attrs)
+{
+       struct dma_map_ops *ops = get_dma_ops(dev);
+       void *cpu_addr;
+       BUG_ON(!ops);
+
+       cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
+       debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
+       return cpu_addr;
+}
 
 /**
- * dma_free_coherent - free memory allocated by dma_alloc_coherent
+ * arm_dma_free - free memory allocated by arm_dma_alloc
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @size: size of memory originally requested in dma_alloc_coherent
  * @cpu_addr: CPU-view address returned from dma_alloc_coherent
  * @handle: device-view address returned from dma_alloc_coherent
+ * @attrs: optinal attributes that specific mapping properties
  *
  * Free (and unmap) a DMA buffer previously allocated by
- * dma_alloc_coherent().
+ * arm_dma_alloc().
  *
  * References to memory and mappings associated with cpu_addr/handle
  * during and after this call executing are illegal.
  */
-extern void dma_free_coherent(struct device *, size_t, void *, dma_addr_t);
+extern void arm_dma_free(struct device *dev, size_t size, void *cpu_addr,
+                        dma_addr_t handle, struct dma_attrs *attrs);
+
+#define dma_free_coherent(d, s, c, h) dma_free_attrs(d, s, c, h, NULL)
+
+static inline void dma_free_attrs(struct device *dev, size_t size,
+                                    void *cpu_addr, dma_addr_t dma_handle,
+                                    struct dma_attrs *attrs)
+{
+       struct dma_map_ops *ops = get_dma_ops(dev);
+       BUG_ON(!ops);
+
+       debug_dma_free_coherent(dev, size, cpu_addr, dma_handle);
+       ops->free(dev, size, cpu_addr, dma_handle, attrs);
+}
 
 /**
- * dma_mmap_coherent - map a coherent DMA allocation into user space
+ * arm_dma_mmap - map a coherent DMA allocation into user space
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @vma: vm_area_struct describing requested user mapping
  * @cpu_addr: kernel CPU-view address returned from dma_alloc_coherent
  * @handle: device-view address returned from dma_alloc_coherent
  * @size: size of memory originally requested in dma_alloc_coherent
+ * @attrs: optinal attributes that specific mapping properties
  *
  * Map a coherent DMA buffer previously allocated by dma_alloc_coherent
  * into user space.  The coherent DMA buffer must not be freed by the
  * driver until the user space mapping has been released.
  */
-int dma_mmap_coherent(struct device *, struct vm_area_struct *,
-               void *, dma_addr_t, size_t);
+extern int arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
+                       void *cpu_addr, dma_addr_t dma_addr, size_t size,
+                       struct dma_attrs *attrs);
 
+#define dma_mmap_coherent(d, v, c, h, s) dma_mmap_attrs(d, v, c, h, s, NULL)
 
-/**
- * dma_alloc_writecombine - allocate writecombining memory for DMA
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @size: required memory size
- * @handle: bus-specific DMA address
- *
- * Allocate some uncached, buffered memory for a device for
- * performing DMA.  This function allocates pages, and will
- * return the CPU-viewed address, and sets @handle to be the
- * device-viewed address.
- */
-extern void *dma_alloc_writecombine(struct device *, size_t, dma_addr_t *,
-               gfp_t);
+static inline int dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
+                                 void *cpu_addr, dma_addr_t dma_addr,
+                                 size_t size, struct dma_attrs *attrs)
+{
+       struct dma_map_ops *ops = get_dma_ops(dev);
+       BUG_ON(!ops);
+       return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
+}
+
+static inline void *dma_alloc_writecombine(struct device *dev, size_t size,
+                                      dma_addr_t *dma_handle, gfp_t flag)
+{
+       DEFINE_DMA_ATTRS(attrs);
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+       return dma_alloc_attrs(dev, size, dma_handle, flag, &attrs);
+}
 
-#define dma_free_writecombine(dev,size,cpu_addr,handle) \
-       dma_free_coherent(dev,size,cpu_addr,handle)
+static inline void dma_free_writecombine(struct device *dev, size_t size,
+                                    void *cpu_addr, dma_addr_t dma_handle)
+{
+       DEFINE_DMA_ATTRS(attrs);
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+       return dma_free_attrs(dev, size, cpu_addr, dma_handle, &attrs);
+}
 
-int dma_mmap_writecombine(struct device *, struct vm_area_struct *,
-               void *, dma_addr_t, size_t);
+static inline int dma_mmap_writecombine(struct device *dev, struct vm_area_struct *vma,
+                     void *cpu_addr, dma_addr_t dma_addr, size_t size)
+{
+       DEFINE_DMA_ATTRS(attrs);
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+       return dma_mmap_attrs(dev, vma, cpu_addr, dma_addr, size, &attrs);
+}
 
 /*
  * This can be called during boot to increase the size of the consistent
  * DMA region above it's default value of 2MB. It must be called before the
  * memory allocator is initialised, i.e. before any core_initcall.
  */
-extern void __init init_consistent_dma_size(unsigned long size);
-
+static inline void init_consistent_dma_size(unsigned long size) { }
 
-#ifdef CONFIG_DMABOUNCE
 /*
  * For SA-1111, IXP425, and ADI systems  the dma-mapping functions are "magic"
  * and utilize bounce buffers as needed to work around limited DMA windows.
@@ -253,222 +267,22 @@ extern int dmabounce_register_dev(struct device *, unsigned long,
  */
 extern void dmabounce_unregister_dev(struct device *);
 
-/*
- * The DMA API, implemented by dmabounce.c.  See below for descriptions.
- */
-extern dma_addr_t __dma_map_page(struct device *, struct page *,
-               unsigned long, size_t, enum dma_data_direction);
-extern void __dma_unmap_page(struct device *, dma_addr_t, size_t,
-               enum dma_data_direction);
-
-/*
- * Private functions
- */
-int dmabounce_sync_for_cpu(struct device *, dma_addr_t, unsigned long,
-               size_t, enum dma_data_direction);
-int dmabounce_sync_for_device(struct device *, dma_addr_t, unsigned long,
-               size_t, enum dma_data_direction);
-#else
-static inline int dmabounce_sync_for_cpu(struct device *d, dma_addr_t addr,
-       unsigned long offset, size_t size, enum dma_data_direction dir)
-{
-       return 1;
-}
 
-static inline int dmabounce_sync_for_device(struct device *d, dma_addr_t addr,
-       unsigned long offset, size_t size, enum dma_data_direction dir)
-{
-       return 1;
-}
-
-
-static inline dma_addr_t __dma_map_page(struct device *dev, struct page *page,
-            unsigned long offset, size_t size, enum dma_data_direction dir)
-{
-       __dma_page_cpu_to_dev(page, offset, size, dir);
-       return pfn_to_dma(dev, page_to_pfn(page)) + offset;
-}
-
-static inline void __dma_unmap_page(struct device *dev, dma_addr_t handle,
-               size_t size, enum dma_data_direction dir)
-{
-       __dma_page_dev_to_cpu(pfn_to_page(dma_to_pfn(dev, handle)),
-               handle & ~PAGE_MASK, size, dir);
-}
-#endif /* CONFIG_DMABOUNCE */
-
-/**
- * dma_map_single - map a single buffer for streaming DMA
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @cpu_addr: CPU direct mapped address of buffer
- * @size: size of buffer to map
- * @dir: DMA transfer direction
- *
- * Ensure that any data held in the cache is appropriately discarded
- * or written back.
- *
- * The device owns this memory once this call has completed.  The CPU
- * can regain ownership by calling dma_unmap_single() or
- * dma_sync_single_for_cpu().
- */
-static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
-               size_t size, enum dma_data_direction dir)
-{
-       unsigned long offset;
-       struct page *page;
-       dma_addr_t addr;
-
-       BUG_ON(!virt_addr_valid(cpu_addr));
-       BUG_ON(!virt_addr_valid(cpu_addr + size - 1));
-       BUG_ON(!valid_dma_direction(dir));
-
-       page = virt_to_page(cpu_addr);
-       offset = (unsigned long)cpu_addr & ~PAGE_MASK;
-       addr = __dma_map_page(dev, page, offset, size, dir);
-       debug_dma_map_page(dev, page, offset, size, dir, addr, true);
-
-       return addr;
-}
-
-/**
- * dma_map_page - map a portion of a page for streaming DMA
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @page: page that buffer resides in
- * @offset: offset into page for start of buffer
- * @size: size of buffer to map
- * @dir: DMA transfer direction
- *
- * Ensure that any data held in the cache is appropriately discarded
- * or written back.
- *
- * The device owns this memory once this call has completed.  The CPU
- * can regain ownership by calling dma_unmap_page().
- */
-static inline dma_addr_t dma_map_page(struct device *dev, struct page *page,
-            unsigned long offset, size_t size, enum dma_data_direction dir)
-{
-       dma_addr_t addr;
-
-       BUG_ON(!valid_dma_direction(dir));
-
-       addr = __dma_map_page(dev, page, offset, size, dir);
-       debug_dma_map_page(dev, page, offset, size, dir, addr, false);
-
-       return addr;
-}
-
-/**
- * dma_unmap_single - unmap a single buffer previously mapped
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @handle: DMA address of buffer
- * @size: size of buffer (same as passed to dma_map_single)
- * @dir: DMA transfer direction (same as passed to dma_map_single)
- *
- * Unmap a single streaming mode DMA translation.  The handle and size
- * must match what was provided in the previous dma_map_single() call.
- * All other usages are undefined.
- *
- * After this call, reads by the CPU to the buffer are guaranteed to see
- * whatever the device wrote there.
- */
-static inline void dma_unmap_single(struct device *dev, dma_addr_t handle,
-               size_t size, enum dma_data_direction dir)
-{
-       debug_dma_unmap_page(dev, handle, size, dir, true);
-       __dma_unmap_page(dev, handle, size, dir);
-}
-
-/**
- * dma_unmap_page - unmap a buffer previously mapped through dma_map_page()
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @handle: DMA address of buffer
- * @size: size of buffer (same as passed to dma_map_page)
- * @dir: DMA transfer direction (same as passed to dma_map_page)
- *
- * Unmap a page streaming mode DMA translation.  The handle and size
- * must match what was provided in the previous dma_map_page() call.
- * All other usages are undefined.
- *
- * After this call, reads by the CPU to the buffer are guaranteed to see
- * whatever the device wrote there.
- */
-static inline void dma_unmap_page(struct device *dev, dma_addr_t handle,
-               size_t size, enum dma_data_direction dir)
-{
-       debug_dma_unmap_page(dev, handle, size, dir, false);
-       __dma_unmap_page(dev, handle, size, dir);
-}
-
-/**
- * dma_sync_single_range_for_cpu
- * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
- * @handle: DMA address of buffer
- * @offset: offset of region to start sync
- * @size: size of region to sync
- * @dir: DMA transfer direction (same as passed to dma_map_single)
- *
- * Make physical memory consistent for a single streaming mode DMA
- * translation after a transfer.
- *
- * If you perform a dma_map_single() but wish to interrogate the
- * buffer using the cpu, yet do not wish to teardown the PCI dma
- * mapping, you must call this function before doing so.  At the
- * next point you give the PCI dma address back to the card, you
- * must first the perform a dma_sync_for_device, and then the
- * device again owns the buffer.
- */
-static inline void dma_sync_single_range_for_cpu(struct device *dev,
-               dma_addr_t handle, unsigned long offset, size_t size,
-               enum dma_data_direction dir)
-{
-       BUG_ON(!valid_dma_direction(dir));
-
-       debug_dma_sync_single_for_cpu(dev, handle + offset, size, dir);
-
-       if (!dmabounce_sync_for_cpu(dev, handle, offset, size, dir))
-               return;
-
-       __dma_single_dev_to_cpu(dma_to_virt(dev, handle) + offset, size, dir);
-}
-
-static inline void dma_sync_single_range_for_device(struct device *dev,
-               dma_addr_t handle, unsigned long offset, size_t size,
-               enum dma_data_direction dir)
-{
-       BUG_ON(!valid_dma_direction(dir));
-
-       debug_dma_sync_single_for_device(dev, handle + offset, size, dir);
-
-       if (!dmabounce_sync_for_device(dev, handle, offset, size, dir))
-               return;
-
-       __dma_single_cpu_to_dev(dma_to_virt(dev, handle) + offset, size, dir);
-}
-
-static inline void dma_sync_single_for_cpu(struct device *dev,
-               dma_addr_t handle, size_t size, enum dma_data_direction dir)
-{
-       dma_sync_single_range_for_cpu(dev, handle, 0, size, dir);
-}
-
-static inline void dma_sync_single_for_device(struct device *dev,
-               dma_addr_t handle, size_t size, enum dma_data_direction dir)
-{
-       dma_sync_single_range_for_device(dev, handle, 0, size, dir);
-}
 
 /*
  * The scatter list versions of the above methods.
  */
-extern int dma_map_sg(struct device *, struct scatterlist *, int,
-               enum dma_data_direction);
-extern void dma_unmap_sg(struct device *, struct scatterlist *, int,
+extern int arm_dma_map_sg(struct device *, struct scatterlist *, int,
+               enum dma_data_direction, struct dma_attrs *attrs);
+extern void arm_dma_unmap_sg(struct device *, struct scatterlist *, int,
+               enum dma_data_direction, struct dma_attrs *attrs);
+extern void arm_dma_sync_sg_for_cpu(struct device *, struct scatterlist *, int,
                enum dma_data_direction);
-extern void dma_sync_sg_for_cpu(struct device *, struct scatterlist *, int,
+extern void arm_dma_sync_sg_for_device(struct device *, struct scatterlist *, int,
                enum dma_data_direction);
-extern void dma_sync_sg_for_device(struct device *, struct scatterlist *, int,
-               enum dma_data_direction);
-
+extern int arm_dma_get_sgtable(struct device *dev, struct sg_table *sgt,
+               void *cpu_addr, dma_addr_t dma_addr, size_t size,
+               struct dma_attrs *attrs);
 
 #endif /* __KERNEL__ */
 #endif
index b36f365..a6efcdd 100644 (file)
@@ -30,6 +30,7 @@ struct map_desc {
 #define MT_MEMORY_DTCM         12
 #define MT_MEMORY_ITCM         13
 #define MT_MEMORY_SO           14
+#define MT_MEMORY_DMA_READY    15
 
 #ifdef CONFIG_MMU
 extern void iotable_init(struct map_desc *, int);
index ebfac78..1b3096d 100644 (file)
@@ -81,6 +81,7 @@ __setup("fpe=", fpe_setup);
 extern void paging_init(struct machine_desc *desc);
 extern void sanity_check_meminfo(void);
 extern void reboot_setup(char *str);
+extern void setup_dma_zone(struct machine_desc *desc);
 
 unsigned int processor_id;
 EXPORT_SYMBOL(processor_id);
@@ -939,12 +940,8 @@ void __init setup_arch(char **cmdline_p)
        machine_desc = mdesc;
        machine_name = mdesc->name;
 
-#ifdef CONFIG_ZONE_DMA
-       if (mdesc->dma_zone_size) {
-               extern unsigned long arm_dma_zone_size;
-               arm_dma_zone_size = mdesc->dma_zone_size;
-       }
-#endif
+       setup_dma_zone(mdesc);
+
        if (mdesc->restart_mode)
                reboot_setup(&mdesc->restart_mode);
 
index db23ae4..b209870 100644 (file)
 #include <linux/init.h>
 #include <linux/device.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma-contiguous.h>
 #include <linux/highmem.h>
+#include <linux/memblock.h>
 #include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/io.h>
+#include <linux/vmalloc.h>
 
 #include <asm/memory.h>
 #include <asm/highmem.h>
 #include <asm/tlbflush.h>
 #include <asm/sizes.h>
 #include <asm/mach/arch.h>
+#include <asm/dma-iommu.h>
+#include <asm/mach/map.h>
+#include <asm/system_info.h>
+#include <asm/dma-contiguous.h>
 
 #include "mm.h"
 
+/*
+ * The DMA API is built upon the notion of "buffer ownership".  A buffer
+ * is either exclusively owned by the CPU (and therefore may be accessed
+ * by it) or exclusively owned by the DMA device.  These helper functions
+ * represent the transitions between these two ownership states.
+ *
+ * Note, however, that on later ARMs, this notion does not work due to
+ * speculative prefetches.  We model our approach on the assumption that
+ * the CPU does do speculative prefetches, which means we clean caches
+ * before transfers and delay cache invalidation until transfer completion.
+ *
+ */
+static void __dma_page_cpu_to_dev(struct page *, unsigned long,
+               size_t, enum dma_data_direction);
+static void __dma_page_dev_to_cpu(struct page *, unsigned long,
+               size_t, enum dma_data_direction);
+
+/**
+ * arm_dma_map_page - map a portion of a page for streaming DMA
+ * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
+ * @page: page that buffer resides in
+ * @offset: offset into page for start of buffer
+ * @size: size of buffer to map
+ * @dir: DMA transfer direction
+ *
+ * Ensure that any data held in the cache is appropriately discarded
+ * or written back.
+ *
+ * The device owns this memory once this call has completed.  The CPU
+ * can regain ownership by calling dma_unmap_page().
+ */
+static dma_addr_t arm_dma_map_page(struct device *dev, struct page *page,
+            unsigned long offset, size_t size, enum dma_data_direction dir,
+            struct dma_attrs *attrs)
+{
+       if (!arch_is_coherent() && !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+               __dma_page_cpu_to_dev(page, offset, size, dir);
+       return pfn_to_dma(dev, page_to_pfn(page)) + offset;
+}
+
+/**
+ * arm_dma_unmap_page - unmap a buffer previously mapped through dma_map_page()
+ * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
+ * @handle: DMA address of buffer
+ * @size: size of buffer (same as passed to dma_map_page)
+ * @dir: DMA transfer direction (same as passed to dma_map_page)
+ *
+ * Unmap a page streaming mode DMA translation.  The handle and size
+ * must match what was provided in the previous dma_map_page() call.
+ * All other usages are undefined.
+ *
+ * After this call, reads by the CPU to the buffer are guaranteed to see
+ * whatever the device wrote there.
+ */
+static void arm_dma_unmap_page(struct device *dev, dma_addr_t handle,
+               size_t size, enum dma_data_direction dir,
+               struct dma_attrs *attrs)
+{
+       if (!arch_is_coherent() && !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+               __dma_page_dev_to_cpu(pfn_to_page(dma_to_pfn(dev, handle)),
+                                     handle & ~PAGE_MASK, size, dir);
+}
+
+static void arm_dma_sync_single_for_cpu(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       unsigned int offset = handle & (PAGE_SIZE - 1);
+       struct page *page = pfn_to_page(dma_to_pfn(dev, handle-offset));
+       if (!arch_is_coherent())
+               __dma_page_dev_to_cpu(page, offset, size, dir);
+}
+
+static void arm_dma_sync_single_for_device(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       unsigned int offset = handle & (PAGE_SIZE - 1);
+       struct page *page = pfn_to_page(dma_to_pfn(dev, handle-offset));
+       if (!arch_is_coherent())
+               __dma_page_cpu_to_dev(page, offset, size, dir);
+}
+
+static int arm_dma_set_mask(struct device *dev, u64 dma_mask);
+
+struct dma_map_ops arm_dma_ops = {
+       .alloc                  = arm_dma_alloc,
+       .free                   = arm_dma_free,
+       .mmap                   = arm_dma_mmap,
+       .get_sgtable            = arm_dma_get_sgtable,
+       .map_page               = arm_dma_map_page,
+       .unmap_page             = arm_dma_unmap_page,
+       .map_sg                 = arm_dma_map_sg,
+       .unmap_sg               = arm_dma_unmap_sg,
+       .sync_single_for_cpu    = arm_dma_sync_single_for_cpu,
+       .sync_single_for_device = arm_dma_sync_single_for_device,
+       .sync_sg_for_cpu        = arm_dma_sync_sg_for_cpu,
+       .sync_sg_for_device     = arm_dma_sync_sg_for_device,
+       .set_dma_mask           = arm_dma_set_mask,
+};
+EXPORT_SYMBOL(arm_dma_ops);
+
 static u64 get_coherent_dma_mask(struct device *dev)
 {
        u64 mask = (u64)arm_dma_limit;
@@ -56,6 +165,21 @@ static u64 get_coherent_dma_mask(struct device *dev)
        return mask;
 }
 
+static void __dma_clear_buffer(struct page *page, size_t size)
+{
+       void *ptr;
+       /*
+        * Ensure that the allocated pages are zeroed, and that any data
+        * lurking in the kernel direct-mapped region is invalidated.
+        */
+       ptr = page_address(page);
+       if (ptr) {
+               memset(ptr, 0, size);
+               dmac_flush_range(ptr, ptr + size);
+               outer_flush_range(__pa(ptr), __pa(ptr) + size);
+       }
+}
+
 /*
  * Allocate a DMA buffer for 'dev' of size 'size' using the
  * specified gfp mask.  Note that 'size' must be page aligned.
@@ -64,23 +188,6 @@ static struct page *__dma_alloc_buffer(struct device *dev, size_t size, gfp_t gf
 {
        unsigned long order = get_order(size);
        struct page *page, *p, *e;
-       void *ptr;
-       u64 mask = get_coherent_dma_mask(dev);
-
-#ifdef CONFIG_DMA_API_DEBUG
-       u64 limit = (mask + 1) & ~mask;
-       if (limit && size >= limit) {
-               dev_warn(dev, "coherent allocation too big (requested %#x mask %#llx)\n",
-                       size, mask);
-               return NULL;
-       }
-#endif
-
-       if (!mask)
-               return NULL;
-
-       if (mask < 0xffffffffULL)
-               gfp |= GFP_DMA;
 
        page = alloc_pages(gfp, order);
        if (!page)
@@ -93,14 +200,7 @@ static struct page *__dma_alloc_buffer(struct device *dev, size_t size, gfp_t gf
        for (p = page + (size >> PAGE_SHIFT), e = page + (1 << order); p < e; p++)
                __free_page(p);
 
-       /*
-        * Ensure that the allocated pages are zeroed, and that any data
-        * lurking in the kernel direct-mapped region is invalidated.
-        */
-       ptr = page_address(page);
-       memset(ptr, 0, size);
-       dmac_flush_range(ptr, ptr + size);
-       outer_flush_range(__pa(ptr), __pa(ptr) + size);
+       __dma_clear_buffer(page, size);
 
        return page;
 }
@@ -119,220 +219,344 @@ static void __dma_free_buffer(struct page *page, size_t size)
 }
 
 #ifdef CONFIG_MMU
+#ifdef CONFIG_HUGETLB_PAGE
+#error ARM Coherent DMA allocator does not (yet) support huge TLB
+#endif
 
-#define CONSISTENT_OFFSET(x)   (((unsigned long)(x) - consistent_base) >> PAGE_SHIFT)
-#define CONSISTENT_PTE_INDEX(x) (((unsigned long)(x) - consistent_base) >> PMD_SHIFT)
-
-/*
- * These are the page tables (2MB each) covering uncached, DMA consistent allocations
- */
-static pte_t **consistent_pte;
-
-#define DEFAULT_CONSISTENT_DMA_SIZE SZ_2M
+static void *__alloc_from_contiguous(struct device *dev, size_t size,
+                                    pgprot_t prot, struct page **ret_page);
 
-unsigned long consistent_base = CONSISTENT_END - DEFAULT_CONSISTENT_DMA_SIZE;
+static void *__alloc_remap_buffer(struct device *dev, size_t size, gfp_t gfp,
+                                pgprot_t prot, struct page **ret_page,
+                                const void *caller);
 
-void __init init_consistent_dma_size(unsigned long size)
+static void *
+__dma_alloc_remap(struct page *page, size_t size, gfp_t gfp, pgprot_t prot,
+       const void *caller)
 {
-       unsigned long base = CONSISTENT_END - ALIGN(size, SZ_2M);
+       struct vm_struct *area;
+       unsigned long addr;
+
+       area = get_vm_area_caller(size, VM_DMA | VM_USERMAP, caller);
+       if (!area)
+               return NULL;
+       addr = (unsigned long)area->addr;
+       area->phys_addr = __pfn_to_phys(page_to_pfn(page));
 
-       BUG_ON(consistent_pte); /* Check we're called before DMA region init */
-       BUG_ON(base < VMALLOC_END);
+       if (ioremap_page_range(addr, addr + size, area->phys_addr, prot)) {
+               vunmap((void *)addr);
+               return NULL;
+       }
+       return (void *)addr;
+}
 
-       /* Grow region to accommodate specified size  */
-       if (base < consistent_base)
-               consistent_base = base;
+static void __dma_free_remap(void *cpu_addr, size_t size)
+{
+       struct vm_struct *area = find_vm_area(cpu_addr);
+       if (!area || !(area->flags & VM_DMA)) {
+               pr_err("%s: trying to free invalid coherent area: %p\n",
+                      __func__, cpu_addr);
+               dump_stack();
+               return;
+       }
+       unmap_kernel_range((unsigned long)cpu_addr, size);
+       vunmap(cpu_addr);
 }
 
-#include "vmregion.h"
+struct dma_pool {
+       size_t size;
+       spinlock_t lock;
+       unsigned long *bitmap;
+       unsigned long nr_pages;
+       void *vaddr;
+       struct page *page;
+};
 
-static struct arm_vmregion_head consistent_head = {
-       .vm_lock        = __SPIN_LOCK_UNLOCKED(&consistent_head.vm_lock),
-       .vm_list        = LIST_HEAD_INIT(consistent_head.vm_list),
-       .vm_end         = CONSISTENT_END,
+static struct dma_pool atomic_pool = {
+       .size = SZ_256K,
 };
 
-#ifdef CONFIG_HUGETLB_PAGE
-#error ARM Coherent DMA allocator does not (yet) support huge TLB
-#endif
+static int __init early_coherent_pool(char *p)
+{
+       atomic_pool.size = memparse(p, &p);
+       return 0;
+}
+early_param("coherent_pool", early_coherent_pool);
 
 /*
- * Initialise the consistent memory allocation.
+ * Initialise the coherent pool for atomic allocations.
  */
-static int __init consistent_init(void)
+static int __init atomic_pool_init(void)
 {
-       int ret = 0;
-       pgd_t *pgd;
-       pud_t *pud;
-       pmd_t *pmd;
-       pte_t *pte;
-       int i = 0;
-       unsigned long base = consistent_base;
-       unsigned long num_ptes = (CONSISTENT_END - base) >> PMD_SHIFT;
+       struct dma_pool *pool = &atomic_pool;
+       pgprot_t prot = pgprot_dmacoherent(pgprot_kernel);
+       unsigned long nr_pages = pool->size >> PAGE_SHIFT;
+       unsigned long *bitmap;
+       struct page *page;
+       void *ptr;
+       int bitmap_size = BITS_TO_LONGS(nr_pages) * sizeof(long);
 
-       consistent_pte = kmalloc(num_ptes * sizeof(pte_t), GFP_KERNEL);
-       if (!consistent_pte) {
-               pr_err("%s: no memory\n", __func__);
-               return -ENOMEM;
+       bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+       if (!bitmap)
+               goto no_bitmap;
+
+       if (IS_ENABLED(CONFIG_CMA))
+               ptr = __alloc_from_contiguous(NULL, pool->size, prot, &page);
+       else
+               ptr = __alloc_remap_buffer(NULL, pool->size, GFP_KERNEL, prot,
+                                          &page, NULL);
+       if (ptr) {
+               spin_lock_init(&pool->lock);
+               pool->vaddr = ptr;
+               pool->page = page;
+               pool->bitmap = bitmap;
+               pool->nr_pages = nr_pages;
+               pr_info("DMA: preallocated %u KiB pool for atomic coherent allocations\n",
+                      (unsigned)pool->size / 1024);
+               return 0;
        }
+       kfree(bitmap);
+no_bitmap:
+       pr_err("DMA: failed to allocate %u KiB pool for atomic coherent allocation\n",
+              (unsigned)pool->size / 1024);
+       return -ENOMEM;
+}
+/*
+ * CMA is activated by core_initcall, so we must be called after it.
+ */
+postcore_initcall(atomic_pool_init);
 
-       pr_debug("DMA memory: 0x%08lx - 0x%08lx:\n", base, CONSISTENT_END);
-       consistent_head.vm_start = base;
+struct dma_contig_early_reserve {
+       phys_addr_t base;
+       unsigned long size;
+};
 
-       do {
-               pgd = pgd_offset(&init_mm, base);
+static struct dma_contig_early_reserve dma_mmu_remap[MAX_CMA_AREAS] __initdata;
 
-               pud = pud_alloc(&init_mm, pgd, base);
-               if (!pud) {
-                       printk(KERN_ERR "%s: no pud tables\n", __func__);
-                       ret = -ENOMEM;
-                       break;
-               }
+static int dma_mmu_remap_num __initdata;
 
-               pmd = pmd_alloc(&init_mm, pud, base);
-               if (!pmd) {
-                       printk(KERN_ERR "%s: no pmd tables\n", __func__);
-                       ret = -ENOMEM;
-                       break;
-               }
-               WARN_ON(!pmd_none(*pmd));
+void __init dma_contiguous_early_fixup(phys_addr_t base, unsigned long size)
+{
+       dma_mmu_remap[dma_mmu_remap_num].base = base;
+       dma_mmu_remap[dma_mmu_remap_num].size = size;
+       dma_mmu_remap_num++;
+}
 
-               pte = pte_alloc_kernel(pmd, base);
-               if (!pte) {
-                       printk(KERN_ERR "%s: no pte tables\n", __func__);
-                       ret = -ENOMEM;
-                       break;
-               }
+void __init dma_contiguous_remap(void)
+{
+       int i;
+       for (i = 0; i < dma_mmu_remap_num; i++) {
+               phys_addr_t start = dma_mmu_remap[i].base;
+               phys_addr_t end = start + dma_mmu_remap[i].size;
+               struct map_desc map;
+               unsigned long addr;
+
+               if (end > arm_lowmem_limit)
+                       end = arm_lowmem_limit;
+               if (start >= end)
+                       return;
+
+               map.pfn = __phys_to_pfn(start);
+               map.virtual = __phys_to_virt(start);
+               map.length = end - start;
+               map.type = MT_MEMORY_DMA_READY;
 
-               consistent_pte[i++] = pte;
-               base += PMD_SIZE;
-       } while (base < CONSISTENT_END);
+               /*
+                * Clear previous low-memory mapping
+                */
+               for (addr = __phys_to_virt(start); addr < __phys_to_virt(end);
+                    addr += PMD_SIZE)
+                       pmd_clear(pmd_off_k(addr));
 
-       return ret;
+               iotable_init(&map, 1);
+       }
 }
 
-core_initcall(consistent_init);
+static int __dma_update_pte(pte_t *pte, pgtable_t token, unsigned long addr,
+                           void *data)
+{
+       struct page *page = virt_to_page(addr);
+       pgprot_t prot = *(pgprot_t *)data;
 
-static void *
-__dma_alloc_remap(struct page *page, size_t size, gfp_t gfp, pgprot_t prot,
-       const void *caller)
+       set_pte_ext(pte, mk_pte(page, prot), 0);
+       return 0;
+}
+
+static void __dma_remap(struct page *page, size_t size, pgprot_t prot)
+{
+       unsigned long start = (unsigned long) page_address(page);
+       unsigned end = start + size;
+
+       apply_to_page_range(&init_mm, start, size, __dma_update_pte, &prot);
+       dsb();
+       flush_tlb_kernel_range(start, end);
+}
+
+static void *__alloc_remap_buffer(struct device *dev, size_t size, gfp_t gfp,
+                                pgprot_t prot, struct page **ret_page,
+                                const void *caller)
+{
+       struct page *page;
+       void *ptr;
+       page = __dma_alloc_buffer(dev, size, gfp);
+       if (!page)
+               return NULL;
+
+       ptr = __dma_alloc_remap(page, size, gfp, prot, caller);
+       if (!ptr) {
+               __dma_free_buffer(page, size);
+               return NULL;
+       }
+
+       *ret_page = page;
+       return ptr;
+}
+
+static void *__alloc_from_pool(size_t size, struct page **ret_page)
 {
-       struct arm_vmregion *c;
+       struct dma_pool *pool = &atomic_pool;
+       unsigned int count = size >> PAGE_SHIFT;
+       unsigned int pageno;
+       unsigned long flags;
+       void *ptr = NULL;
        size_t align;
-       int bit;
 
-       if (!consistent_pte) {
-               printk(KERN_ERR "%s: not initialised\n", __func__);
+       if (!pool->vaddr) {
+               pr_err("%s: coherent pool not initialised!\n", __func__);
                dump_stack();
                return NULL;
        }
 
        /*
-        * Align the virtual region allocation - maximum alignment is
-        * a section size, minimum is a page size.  This helps reduce
-        * fragmentation of the DMA space, and also prevents allocations
-        * smaller than a section from crossing a section boundary.
-        */
-       bit = fls(size - 1);
-       if (bit > SECTION_SHIFT)
-               bit = SECTION_SHIFT;
-       align = 1 << bit;
-
-       /*
-        * Allocate a virtual address in the consistent mapping region.
+        * Align the region allocation - allocations from pool are rather
+        * small, so align them to their order in pages, minimum is a page
+        * size. This helps reduce fragmentation of the DMA space.
         */
-       c = arm_vmregion_alloc(&consistent_head, align, size,
-                           gfp & ~(__GFP_DMA | __GFP_HIGHMEM), caller);
-       if (c) {
-               pte_t *pte;
-               int idx = CONSISTENT_PTE_INDEX(c->vm_start);
-               u32 off = CONSISTENT_OFFSET(c->vm_start) & (PTRS_PER_PTE-1);
-
-               pte = consistent_pte[idx] + off;
-               c->vm_pages = page;
-
-               do {
-                       BUG_ON(!pte_none(*pte));
-
-                       set_pte_ext(pte, mk_pte(page, prot), 0);
-                       page++;
-                       pte++;
-                       off++;
-                       if (off >= PTRS_PER_PTE) {
-                               off = 0;
-                               pte = consistent_pte[++idx];
-                       }
-               } while (size -= PAGE_SIZE);
-
-               dsb();
-
-               return (void *)c->vm_start;
+       align = PAGE_SIZE << get_order(size);
+
+       spin_lock_irqsave(&pool->lock, flags);
+       pageno = bitmap_find_next_zero_area(pool->bitmap, pool->nr_pages,
+                                           0, count, (1 << align) - 1);
+       if (pageno < pool->nr_pages) {
+               bitmap_set(pool->bitmap, pageno, count);
+               ptr = pool->vaddr + PAGE_SIZE * pageno;
+               *ret_page = pool->page + pageno;
        }
-       return NULL;
+       spin_unlock_irqrestore(&pool->lock, flags);
+
+       return ptr;
 }
 
-static void __dma_free_remap(void *cpu_addr, size_t size)
+static int __free_from_pool(void *start, size_t size)
 {
-       struct arm_vmregion *c;
-       unsigned long addr;
-       pte_t *ptep;
-       int idx;
-       u32 off;
+       struct dma_pool *pool = &atomic_pool;
+       unsigned long pageno, count;
+       unsigned long flags;
 
-       c = arm_vmregion_find_remove(&consistent_head, (unsigned long)cpu_addr);
-       if (!c) {
-               printk(KERN_ERR "%s: trying to free invalid coherent area: %p\n",
-                      __func__, cpu_addr);
-               dump_stack();
-               return;
-       }
+       if (start < pool->vaddr || start > pool->vaddr + pool->size)
+               return 0;
 
-       if ((c->vm_end - c->vm_start) != size) {
-               printk(KERN_ERR "%s: freeing wrong coherent size (%ld != %d)\n",
-                      __func__, c->vm_end - c->vm_start, size);
+       if (start + size > pool->vaddr + pool->size) {
+               pr_err("%s: freeing wrong coherent size from pool\n", __func__);
                dump_stack();
-               size = c->vm_end - c->vm_start;
+               return 0;
        }
 
-       idx = CONSISTENT_PTE_INDEX(c->vm_start);
-       off = CONSISTENT_OFFSET(c->vm_start) & (PTRS_PER_PTE-1);
-       ptep = consistent_pte[idx] + off;
-       addr = c->vm_start;
-       do {
-               pte_t pte = ptep_get_and_clear(&init_mm, addr, ptep);
-
-               ptep++;
-               addr += PAGE_SIZE;
-               off++;
-               if (off >= PTRS_PER_PTE) {
-                       off = 0;
-                       ptep = consistent_pte[++idx];
-               }
+       pageno = (start - pool->vaddr) >> PAGE_SHIFT;
+       count = size >> PAGE_SHIFT;
 
-               if (pte_none(pte) || !pte_present(pte))
-                       printk(KERN_CRIT "%s: bad page in kernel page table\n",
-                              __func__);
-       } while (size -= PAGE_SIZE);
+       spin_lock_irqsave(&pool->lock, flags);
+       bitmap_clear(pool->bitmap, pageno, count);
+       spin_unlock_irqrestore(&pool->lock, flags);
 
-       flush_tlb_kernel_range(c->vm_start, c->vm_end);
+       return 1;
+}
+
+static void *__alloc_from_contiguous(struct device *dev, size_t size,
+                                    pgprot_t prot, struct page **ret_page)
+{
+       unsigned long order = get_order(size);
+       size_t count = size >> PAGE_SHIFT;
+       struct page *page;
+
+       page = dma_alloc_from_contiguous(dev, count, order);
+       if (!page)
+               return NULL;
+
+       __dma_clear_buffer(page, size);
+       __dma_remap(page, size, prot);
+
+       *ret_page = page;
+       return page_address(page);
+}
 
-       arm_vmregion_free(&consistent_head, c);
+static void __free_from_contiguous(struct device *dev, struct page *page,
+                                  size_t size)
+{
+       __dma_remap(page, size, pgprot_kernel);
+       dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT);
+}
+
+static inline pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot)
+{
+       prot = dma_get_attr(DMA_ATTR_WRITE_COMBINE, attrs) ?
+                           pgprot_writecombine(prot) :
+                           pgprot_dmacoherent(prot);
+       return prot;
 }
 
+#define nommu() 0
+
 #else  /* !CONFIG_MMU */
 
-#define __dma_alloc_remap(page, size, gfp, prot, c)    page_address(page)
-#define __dma_free_remap(addr, size)                   do { } while (0)
+#define nommu() 1
+
+#define __get_dma_pgprot(attrs, prot)  __pgprot(0)
+#define __alloc_remap_buffer(dev, size, gfp, prot, ret, c)     NULL
+#define __alloc_from_pool(dev, size, ret_page, c)              NULL
+#define __alloc_from_contiguous(dev, size, prot, ret)          NULL
+#define __free_from_pool(cpu_addr, size)                       0
+#define __free_from_contiguous(dev, page, size)                        do { } while (0)
+#define __dma_free_remap(cpu_addr, size)                       do { } while (0)
 
 #endif /* CONFIG_MMU */
 
-static void *
-__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp,
-           pgprot_t prot, const void *caller)
+static void *__alloc_simple_buffer(struct device *dev, size_t size, gfp_t gfp,
+                                  struct page **ret_page)
+{
+       struct page *page;
+       page = __dma_alloc_buffer(dev, size, gfp);
+       if (!page)
+               return NULL;
+
+       *ret_page = page;
+       return page_address(page);
+}
+
+
+
+static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
+                        gfp_t gfp, pgprot_t prot, const void *caller)
 {
+       u64 mask = get_coherent_dma_mask(dev);
        struct page *page;
        void *addr;
 
+#ifdef CONFIG_DMA_API_DEBUG
+       u64 limit = (mask + 1) & ~mask;
+       if (limit && size >= limit) {
+               dev_warn(dev, "coherent allocation too big (requested %#x mask %#llx)\n",
+                       size, mask);
+               return NULL;
+       }
+#endif
+
+       if (!mask)
+               return NULL;
+
+       if (mask < 0xffffffffULL)
+               gfp |= GFP_DMA;
+
        /*
         * Following is a work-around (a.k.a. hack) to prevent pages
         * with __GFP_COMP being passed to split_page() which cannot
@@ -342,22 +566,20 @@ __dma_alloc(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp,
         */
        gfp &= ~(__GFP_COMP);
 
-       *handle = ~0;
+       *handle = DMA_ERROR_CODE;
        size = PAGE_ALIGN(size);
 
-       page = __dma_alloc_buffer(dev, size, gfp);
-       if (!page)
-               return NULL;
-
-       if (!arch_is_coherent())
-               addr = __dma_alloc_remap(page, size, gfp, prot, caller);
+       if (arch_is_coherent() || nommu())
+               addr = __alloc_simple_buffer(dev, size, gfp, &page);
+       else if (gfp & GFP_ATOMIC)
+               addr = __alloc_from_pool(size, &page);
+       else if (!IS_ENABLED(CONFIG_CMA))
+               addr = __alloc_remap_buffer(dev, size, gfp, prot, &page, caller);
        else
-               addr = page_address(page);
+               addr = __alloc_from_contiguous(dev, size, prot, &page);
 
        if (addr)
                *handle = pfn_to_dma(dev, page_to_pfn(page));
-       else
-               __dma_free_buffer(page, size);
 
        return addr;
 }
@@ -366,138 +588,92 @@ __dma_alloc(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp,
  * Allocate DMA-coherent memory space and return both the kernel remapped
  * virtual and bus address for that space.
  */
-void *
-dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
+void *arm_dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
+                   gfp_t gfp, struct dma_attrs *attrs)
 {
+       pgprot_t prot = __get_dma_pgprot(attrs, pgprot_kernel);
        void *memory;
 
        if (dma_alloc_from_coherent(dev, size, handle, &memory))
                return memory;
 
-       return __dma_alloc(dev, size, handle, gfp,
-                          pgprot_dmacoherent(pgprot_kernel),
+       return __dma_alloc(dev, size, handle, gfp, prot,
                           __builtin_return_address(0));
 }
-EXPORT_SYMBOL(dma_alloc_coherent);
 
 /*
- * Allocate a writecombining region, in much the same way as
- * dma_alloc_coherent above.
+ * Create userspace mapping for the DMA-coherent memory.
  */
-void *
-dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
-{
-       return __dma_alloc(dev, size, handle, gfp,
-                          pgprot_writecombine(pgprot_kernel),
-                          __builtin_return_address(0));
-}
-EXPORT_SYMBOL(dma_alloc_writecombine);
-
-static int dma_mmap(struct device *dev, struct vm_area_struct *vma,
-                   void *cpu_addr, dma_addr_t dma_addr, size_t size)
+int arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
+                void *cpu_addr, dma_addr_t dma_addr, size_t size,
+                struct dma_attrs *attrs)
 {
        int ret = -ENXIO;
 #ifdef CONFIG_MMU
-       unsigned long user_size, kern_size;
-       struct arm_vmregion *c;
+       unsigned long user_count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+       unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+       unsigned long pfn = dma_to_pfn(dev, dma_addr);
+       unsigned long off = vma->vm_pgoff;
 
-       user_size = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+       vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot);
 
-       c = arm_vmregion_find(&consistent_head, (unsigned long)cpu_addr);
-       if (c) {
-               unsigned long off = vma->vm_pgoff;
+       if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
+               return ret;
 
-               kern_size = (c->vm_end - c->vm_start) >> PAGE_SHIFT;
-
-               if (off < kern_size &&
-                   user_size <= (kern_size - off)) {
-                       ret = remap_pfn_range(vma, vma->vm_start,
-                                             page_to_pfn(c->vm_pages) + off,
-                                             user_size << PAGE_SHIFT,
-                                             vma->vm_page_prot);
-               }
+       if (off < count && user_count <= (count - off)) {
+               ret = remap_pfn_range(vma, vma->vm_start,
+                                     pfn + off,
+                                     user_count << PAGE_SHIFT,
+                                     vma->vm_page_prot);
        }
 #endif /* CONFIG_MMU */
 
        return ret;
 }
 
-int dma_mmap_coherent(struct device *dev, struct vm_area_struct *vma,
-                     void *cpu_addr, dma_addr_t dma_addr, size_t size)
-{
-       vma->vm_page_prot = pgprot_dmacoherent(vma->vm_page_prot);
-       return dma_mmap(dev, vma, cpu_addr, dma_addr, size);
-}
-EXPORT_SYMBOL(dma_mmap_coherent);
-
-int dma_mmap_writecombine(struct device *dev, struct vm_area_struct *vma,
-                         void *cpu_addr, dma_addr_t dma_addr, size_t size)
-{
-       vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
-       return dma_mmap(dev, vma, cpu_addr, dma_addr, size);
-}
-EXPORT_SYMBOL(dma_mmap_writecombine);
-
 /*
- * free a page as defined by the above mapping.
- * Must not be called with IRQs disabled.
+ * Free a buffer as defined by the above mapping.
  */
-void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)
+void arm_dma_free(struct device *dev, size_t size, void *cpu_addr,
+                 dma_addr_t handle, struct dma_attrs *attrs)
 {
-       WARN_ON(irqs_disabled());
+       struct page *page = pfn_to_page(dma_to_pfn(dev, handle));
 
        if (dma_release_from_coherent(dev, get_order(size), cpu_addr))
                return;
 
        size = PAGE_ALIGN(size);
 
-       if (!arch_is_coherent())
+       if (arch_is_coherent() || nommu()) {
+               __dma_free_buffer(page, size);
+       } else if (cpu_architecture() < CPU_ARCH_ARMv6) {
                __dma_free_remap(cpu_addr, size);
-
-       __dma_free_buffer(pfn_to_page(dma_to_pfn(dev, handle)), size);
-}
-EXPORT_SYMBOL(dma_free_coherent);
-
-/*
- * Make an area consistent for devices.
- * Note: Drivers should NOT use this function directly, as it will break
- * platforms with CONFIG_DMABOUNCE.
- * Use the driver DMA support - see dma-mapping.h (dma_sync_*)
- */
-void ___dma_single_cpu_to_dev(const void *kaddr, size_t size,
-       enum dma_data_direction dir)
-{
-       unsigned long paddr;
-
-       BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));
-
-       dmac_map_area(kaddr, size, dir);
-
-       paddr = __pa(kaddr);
-       if (dir == DMA_FROM_DEVICE) {
-               outer_inv_range(paddr, paddr + size);
+               __dma_free_buffer(page, size);
        } else {
-               outer_clean_range(paddr, paddr + size);
+               if (__free_from_pool(cpu_addr, size))
+                       return;
+               /*
+                * Non-atomic allocations cannot be freed with IRQs disabled
+                */
+               WARN_ON(irqs_disabled());
+               __free_from_contiguous(dev, page, size);
        }
-       /* FIXME: non-speculating: flush on bidirectional mappings? */
 }
-EXPORT_SYMBOL(___dma_single_cpu_to_dev);
 
-void ___dma_single_dev_to_cpu(const void *kaddr, size_t size,
-       enum dma_data_direction dir)
+int arm_dma_get_sgtable(struct device *dev, struct sg_table *sgt,
+                void *cpu_addr, dma_addr_t handle, size_t size,
+                struct dma_attrs *attrs)
 {
-       BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));
+       struct page *page = pfn_to_page(dma_to_pfn(dev, handle));
+       int ret;
 
-       /* FIXME: non-speculating: not required */
-       /* don't bother invalidating if DMA to device */
-       if (dir != DMA_TO_DEVICE) {
-               unsigned long paddr = __pa(kaddr);
-               outer_inv_range(paddr, paddr + size);
-       }
+       ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+       if (unlikely(ret))
+               return ret;
 
-       dmac_unmap_area(kaddr, size, dir);
+       sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
+       return 0;
 }
-EXPORT_SYMBOL(___dma_single_dev_to_cpu);
 
 static void dma_cache_maint_page(struct page *page, unsigned long offset,
        size_t size, enum dma_data_direction dir,
@@ -543,9 +719,15 @@ static void dma_cache_maint_page(struct page *page, unsigned long offset,
        } while (left);
 }
 
-void ___dma_page_cpu_to_dev(struct page *page, unsigned long off,
-       size_t size, enum dma_data_direction dir)
-{
+/*
+ * Make an area consistent for devices.
+ * Note: Drivers should NOT use this function directly, as it will break
+ * platforms with CONFIG_DMABOUNCE.
+ * Use the driver DMA support - see dma-mapping.h (dma_sync_*)
+ */
+static void __dma_page_cpu_to_dev(struct page *page, unsigned long off,
+       size_t size, enum dma_data_direction dir)
+{
        unsigned long paddr;
 
        dma_cache_maint_page(page, off, size, dir, dmac_map_area);
@@ -558,9 +740,8 @@ void ___dma_page_cpu_to_dev(struct page *page, unsigned long off,
        }
        /* FIXME: non-speculating: flush on bidirectional mappings? */
 }
-EXPORT_SYMBOL(___dma_page_cpu_to_dev);
 
-void ___dma_page_dev_to_cpu(struct page *page, unsigned long off,
+static void __dma_page_dev_to_cpu(struct page *page, unsigned long off,
        size_t size, enum dma_data_direction dir)
 {
        unsigned long paddr = page_to_phys(page) + off;
@@ -578,10 +759,9 @@ void ___dma_page_dev_to_cpu(struct page *page, unsigned long off,
        if (dir != DMA_TO_DEVICE && off == 0 && size >= PAGE_SIZE)
                set_bit(PG_dcache_clean, &page->flags);
 }
-EXPORT_SYMBOL(___dma_page_dev_to_cpu);
 
 /**
- * dma_map_sg - map a set of SG buffers for streaming mode DMA
+ * arm_dma_map_sg - map a set of SG buffers for streaming mode DMA
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @sg: list of buffers
  * @nents: number of buffers to map
@@ -596,32 +776,32 @@ EXPORT_SYMBOL(___dma_page_dev_to_cpu);
  * Device ownership issues as mentioned for dma_map_single are the same
  * here.
  */
-int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
-               enum dma_data_direction dir)
+int arm_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
+               enum dma_data_direction dir, struct dma_attrs *attrs)
 {
+       struct dma_map_ops *ops = get_dma_ops(dev);
        struct scatterlist *s;
        int i, j;
 
-       BUG_ON(!valid_dma_direction(dir));
-
        for_each_sg(sg, s, nents, i) {
-               s->dma_address = __dma_map_page(dev, sg_page(s), s->offset,
-                                               s->length, dir);
+#ifdef CONFIG_NEED_SG_DMA_LENGTH
+               s->dma_length = s->length;
+#endif
+               s->dma_address = ops->map_page(dev, sg_page(s), s->offset,
+                                               s->length, dir, attrs);
                if (dma_mapping_error(dev, s->dma_address))
                        goto bad_mapping;
        }
-       debug_dma_map_sg(dev, sg, nents, nents, dir);
        return nents;
 
  bad_mapping:
        for_each_sg(sg, s, i, j)
-               __dma_unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir);
+               ops->unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir, attrs);
        return 0;
 }
-EXPORT_SYMBOL(dma_map_sg);
 
 /**
- * dma_unmap_sg - unmap a set of SG buffers mapped by dma_map_sg
+ * arm_dma_unmap_sg - unmap a set of SG buffers mapped by dma_map_sg
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @sg: list of buffers
  * @nents: number of buffers to unmap (same as was passed to dma_map_sg)
@@ -630,70 +810,55 @@ EXPORT_SYMBOL(dma_map_sg);
  * Unmap a set of streaming mode DMA translations.  Again, CPU access
  * rules concerning calls here are the same as for dma_unmap_single().
  */
-void dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
-               enum dma_data_direction dir)
+void arm_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
+               enum dma_data_direction dir, struct dma_attrs *attrs)
 {
+       struct dma_map_ops *ops = get_dma_ops(dev);
        struct scatterlist *s;
-       int i;
 
-       debug_dma_unmap_sg(dev, sg, nents, dir);
+       int i;
 
        for_each_sg(sg, s, nents, i)
-               __dma_unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir);
+               ops->unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir, attrs);
 }
-EXPORT_SYMBOL(dma_unmap_sg);
 
 /**
- * dma_sync_sg_for_cpu
+ * arm_dma_sync_sg_for_cpu
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @sg: list of buffers
  * @nents: number of buffers to map (returned from dma_map_sg)
  * @dir: DMA transfer direction (same as was passed to dma_map_sg)
  */
-void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
+void arm_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
                        int nents, enum dma_data_direction dir)
 {
+       struct dma_map_ops *ops = get_dma_ops(dev);
        struct scatterlist *s;
        int i;
 
-       for_each_sg(sg, s, nents, i) {
-               if (!dmabounce_sync_for_cpu(dev, sg_dma_address(s), 0,
-                                           sg_dma_len(s), dir))
-                       continue;
-
-               __dma_page_dev_to_cpu(sg_page(s), s->offset,
-                                     s->length, dir);
-       }
-
-       debug_dma_sync_sg_for_cpu(dev, sg, nents, dir);
+       for_each_sg(sg, s, nents, i)
+               ops->sync_single_for_cpu(dev, sg_dma_address(s), s->length,
+                                        dir);
 }
-EXPORT_SYMBOL(dma_sync_sg_for_cpu);
 
 /**
- * dma_sync_sg_for_device
+ * arm_dma_sync_sg_for_device
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
  * @sg: list of buffers
  * @nents: number of buffers to map (returned from dma_map_sg)
  * @dir: DMA transfer direction (same as was passed to dma_map_sg)
  */
-void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
+void arm_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
                        int nents, enum dma_data_direction dir)
 {
+       struct dma_map_ops *ops = get_dma_ops(dev);
        struct scatterlist *s;
        int i;
 
-       for_each_sg(sg, s, nents, i) {
-               if (!dmabounce_sync_for_device(dev, sg_dma_address(s), 0,
-                                       sg_dma_len(s), dir))
-                       continue;
-
-               __dma_page_cpu_to_dev(sg_page(s), s->offset,
-                                     s->length, dir);
-       }
-
-       debug_dma_sync_sg_for_device(dev, sg, nents, dir);
+       for_each_sg(sg, s, nents, i)
+               ops->sync_single_for_device(dev, sg_dma_address(s), s->length,
+                                           dir);
 }
-EXPORT_SYMBOL(dma_sync_sg_for_device);
 
 /*
  * Return whether the given device DMA address mask can be supported
@@ -709,27 +874,702 @@ int dma_supported(struct device *dev, u64 mask)
 }
 EXPORT_SYMBOL(dma_supported);
 
-int dma_set_mask(struct device *dev, u64 dma_mask)
+static int arm_dma_set_mask(struct device *dev, u64 dma_mask)
 {
        if (!dev->dma_mask || !dma_supported(dev, dma_mask))
                return -EIO;
 
-#ifndef CONFIG_DMABOUNCE
        *dev->dma_mask = dma_mask;
-#endif
 
        return 0;
 }
-EXPORT_SYMBOL(dma_set_mask);
 
 #define PREALLOC_DMA_DEBUG_ENTRIES     4096
 
 static int __init dma_debug_do_init(void)
 {
-#ifdef CONFIG_MMU
-       arm_vmregion_create_proc("dma-mappings", &consistent_head);
-#endif
        dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);
        return 0;
 }
 fs_initcall(dma_debug_do_init);
+
+#ifdef CONFIG_ARM_DMA_USE_IOMMU
+
+/* IOMMU */
+
+static inline dma_addr_t __alloc_iova(struct dma_iommu_mapping *mapping,
+                                     size_t size)
+{
+       unsigned int order = get_order(size);
+       unsigned int align = 0;
+       unsigned int count, start;
+       unsigned long flags;
+
+       count = ((PAGE_ALIGN(size) >> PAGE_SHIFT) +
+                (1 << mapping->order) - 1) >> mapping->order;
+
+       if (order > mapping->order)
+               align = (1 << (order - mapping->order)) - 1;
+
+       spin_lock_irqsave(&mapping->lock, flags);
+       start = bitmap_find_next_zero_area(mapping->bitmap, mapping->bits, 0,
+                                          count, align);
+       if (start > mapping->bits) {
+               spin_unlock_irqrestore(&mapping->lock, flags);
+               return DMA_ERROR_CODE;
+       }
+
+       bitmap_set(mapping->bitmap, start, count);
+       spin_unlock_irqrestore(&mapping->lock, flags);
+
+       return mapping->base + (start << (mapping->order + PAGE_SHIFT));
+}
+
+static inline void __free_iova(struct dma_iommu_mapping *mapping,
+                              dma_addr_t addr, size_t size)
+{
+       unsigned int start = (addr - mapping->base) >>
+                            (mapping->order + PAGE_SHIFT);
+       unsigned int count = ((size >> PAGE_SHIFT) +
+                             (1 << mapping->order) - 1) >> mapping->order;
+       unsigned long flags;
+
+       spin_lock_irqsave(&mapping->lock, flags);
+       bitmap_clear(mapping->bitmap, start, count);
+       spin_unlock_irqrestore(&mapping->lock, flags);
+}
+
+static struct page **__iommu_alloc_buffer(struct device *dev, size_t size, gfp_t gfp)
+{
+       struct page **pages;
+       int count = size >> PAGE_SHIFT;
+       int array_size = count * sizeof(struct page *);
+       int i = 0;
+
+       if (array_size <= PAGE_SIZE)
+               pages = kzalloc(array_size, gfp);
+       else
+               pages = vzalloc(array_size);
+       if (!pages)
+               return NULL;
+
+       while (count) {
+               int j, order = __ffs(count);
+
+               pages[i] = alloc_pages(gfp | __GFP_NOWARN, order);
+               while (!pages[i] && order)
+                       pages[i] = alloc_pages(gfp | __GFP_NOWARN, --order);
+               if (!pages[i])
+                       goto error;
+
+               if (order)
+                       split_page(pages[i], order);
+               j = 1 << order;
+               while (--j)
+                       pages[i + j] = pages[i] + j;
+
+               __dma_clear_buffer(pages[i], PAGE_SIZE << order);
+               i += 1 << order;
+               count -= 1 << order;
+       }
+
+       return pages;
+error:
+       while (--i)
+               if (pages[i])
+                       __free_pages(pages[i], 0);
+       if (array_size < PAGE_SIZE)
+               kfree(pages);
+       else
+               vfree(pages);
+       return NULL;
+}
+
+static int __iommu_free_buffer(struct device *dev, struct page **pages, size_t size)
+{
+       int count = size >> PAGE_SHIFT;
+       int array_size = count * sizeof(struct page *);
+       int i;
+       for (i = 0; i < count; i++)
+               if (pages[i])
+                       __free_pages(pages[i], 0);
+       if (array_size < PAGE_SIZE)
+               kfree(pages);
+       else
+               vfree(pages);
+       return 0;
+}
+
+/*
+ * Create a CPU mapping for a specified pages
+ */
+static void *
+__iommu_alloc_remap(struct page **pages, size_t size, gfp_t gfp, pgprot_t prot,
+                   const void *caller)
+{
+       unsigned int i, nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
+       struct vm_struct *area;
+       unsigned long p;
+
+       area = get_vm_area_caller(size, VM_DMA | VM_USERMAP, caller);
+       if (!area)
+               return NULL;
+
+       area->pages = pages;
+       area->nr_pages = nr_pages;
+       p = (unsigned long)area->addr;
+
+       for (i = 0; i < nr_pages; i++) {
+               phys_addr_t phys = __pfn_to_phys(page_to_pfn(pages[i]));
+               if (ioremap_page_range(p, p + PAGE_SIZE, phys, prot))
+                       goto err;
+               p += PAGE_SIZE;
+       }
+       return area->addr;
+err:
+       unmap_kernel_range((unsigned long)area->addr, size);
+       vunmap(area->addr);
+       return NULL;
+}
+
+/*
+ * Create a mapping in device IO address space for specified pages
+ */
+static dma_addr_t
+__iommu_create_mapping(struct device *dev, struct page **pages, size_t size)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+       dma_addr_t dma_addr, iova;
+       int i, ret = DMA_ERROR_CODE;
+
+       dma_addr = __alloc_iova(mapping, size);
+       if (dma_addr == DMA_ERROR_CODE)
+               return dma_addr;
+
+       iova = dma_addr;
+       for (i = 0; i < count; ) {
+               unsigned int next_pfn = page_to_pfn(pages[i]) + 1;
+               phys_addr_t phys = page_to_phys(pages[i]);
+               unsigned int len, j;
+
+               for (j = i + 1; j < count; j++, next_pfn++)
+                       if (page_to_pfn(pages[j]) != next_pfn)
+                               break;
+
+               len = (j - i) << PAGE_SHIFT;
+               ret = iommu_map(mapping->domain, iova, phys, len, 0);
+               if (ret < 0)
+                       goto fail;
+               iova += len;
+               i = j;
+       }
+       return dma_addr;
+fail:
+       iommu_unmap(mapping->domain, dma_addr, iova-dma_addr);
+       __free_iova(mapping, dma_addr, size);
+       return DMA_ERROR_CODE;
+}
+
+static int __iommu_remove_mapping(struct device *dev, dma_addr_t iova, size_t size)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+
+       /*
+        * add optional in-page offset from iova to size and align
+        * result to page size
+        */
+       size = PAGE_ALIGN((iova & ~PAGE_MASK) + size);
+       iova &= PAGE_MASK;
+
+       iommu_unmap(mapping->domain, iova, size);
+       __free_iova(mapping, iova, size);
+       return 0;
+}
+
+static struct page **__iommu_get_pages(void *cpu_addr, struct dma_attrs *attrs)
+{
+       struct vm_struct *area;
+
+       if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
+               return cpu_addr;
+
+       area = find_vm_area(cpu_addr);
+       if (area && (area->flags & VM_DMA))
+               return area->pages;
+       return NULL;
+}
+
+static void *arm_iommu_alloc_attrs(struct device *dev, size_t size,
+           dma_addr_t *handle, gfp_t gfp, struct dma_attrs *attrs)
+{
+       pgprot_t prot = __get_dma_pgprot(attrs, pgprot_kernel);
+       struct page **pages;
+       void *addr = NULL;
+
+       *handle = DMA_ERROR_CODE;
+       size = PAGE_ALIGN(size);
+
+       pages = __iommu_alloc_buffer(dev, size, gfp);
+       if (!pages)
+               return NULL;
+
+       *handle = __iommu_create_mapping(dev, pages, size);
+       if (*handle == DMA_ERROR_CODE)
+               goto err_buffer;
+
+       if (dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs))
+               return pages;
+
+       addr = __iommu_alloc_remap(pages, size, gfp, prot,
+                                  __builtin_return_address(0));
+       if (!addr)
+               goto err_mapping;
+
+       return addr;
+
+err_mapping:
+       __iommu_remove_mapping(dev, *handle, size);
+err_buffer:
+       __iommu_free_buffer(dev, pages, size);
+       return NULL;
+}
+
+static int arm_iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
+                   void *cpu_addr, dma_addr_t dma_addr, size_t size,
+                   struct dma_attrs *attrs)
+{
+       unsigned long uaddr = vma->vm_start;
+       unsigned long usize = vma->vm_end - vma->vm_start;
+       struct page **pages = __iommu_get_pages(cpu_addr, attrs);
+
+       vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot);
+
+       if (!pages)
+               return -ENXIO;
+
+       do {
+               int ret = vm_insert_page(vma, uaddr, *pages++);
+               if (ret) {
+                       pr_err("Remapping memory failed: %d\n", ret);
+                       return ret;
+               }
+               uaddr += PAGE_SIZE;
+               usize -= PAGE_SIZE;
+       } while (usize > 0);
+
+       return 0;
+}
+
+/*
+ * free a page as defined by the above mapping.
+ * Must not be called with IRQs disabled.
+ */
+void arm_iommu_free_attrs(struct device *dev, size_t size, void *cpu_addr,
+                         dma_addr_t handle, struct dma_attrs *attrs)
+{
+       struct page **pages = __iommu_get_pages(cpu_addr, attrs);
+       size = PAGE_ALIGN(size);
+
+       if (!pages) {
+               pr_err("%s: trying to free invalid coherent area: %p\n",
+                      __func__, cpu_addr);
+               dump_stack();
+               return;
+       }
+
+       if (!dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs)) {
+               unmap_kernel_range((unsigned long)cpu_addr, size);
+               vunmap(cpu_addr);
+       }
+
+       __iommu_remove_mapping(dev, handle, size);
+       __iommu_free_buffer(dev, pages, size);
+}
+
+static int arm_iommu_get_sgtable(struct device *dev, struct sg_table *sgt,
+                                void *cpu_addr, dma_addr_t dma_addr,
+                                size_t size, struct dma_attrs *attrs)
+{
+       unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+       struct page **pages = __iommu_get_pages(cpu_addr, attrs);
+
+       if (!pages)
+               return -ENXIO;
+
+       return sg_alloc_table_from_pages(sgt, pages, count, 0, size,
+                                        GFP_KERNEL);
+}
+
+/*
+ * Map a part of the scatter-gather list into contiguous io address space
+ */
+static int __map_sg_chunk(struct device *dev, struct scatterlist *sg,
+                         size_t size, dma_addr_t *handle,
+                         enum dma_data_direction dir, struct dma_attrs *attrs)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       dma_addr_t iova, iova_base;
+       int ret = 0;
+       unsigned int count;
+       struct scatterlist *s;
+
+       size = PAGE_ALIGN(size);
+       *handle = DMA_ERROR_CODE;
+
+       iova_base = iova = __alloc_iova(mapping, size);
+       if (iova == DMA_ERROR_CODE)
+               return -ENOMEM;
+
+       for (count = 0, s = sg; count < (size >> PAGE_SHIFT); s = sg_next(s)) {
+               phys_addr_t phys = page_to_phys(sg_page(s));
+               unsigned int len = PAGE_ALIGN(s->offset + s->length);
+
+               if (!arch_is_coherent() &&
+                   !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+                       __dma_page_cpu_to_dev(sg_page(s), s->offset, s->length, dir);
+
+               ret = iommu_map(mapping->domain, iova, phys, len, 0);
+               if (ret < 0)
+                       goto fail;
+               count += len >> PAGE_SHIFT;
+               iova += len;
+       }
+       *handle = iova_base;
+
+       return 0;
+fail:
+       iommu_unmap(mapping->domain, iova_base, count * PAGE_SIZE);
+       __free_iova(mapping, iova_base, size);
+       return ret;
+}
+
+/**
+ * arm_iommu_map_sg - map a set of SG buffers for streaming mode DMA
+ * @dev: valid struct device pointer
+ * @sg: list of buffers
+ * @nents: number of buffers to map
+ * @dir: DMA transfer direction
+ *
+ * Map a set of buffers described by scatterlist in streaming mode for DMA.
+ * The scatter gather list elements are merged together (if possible) and
+ * tagged with the appropriate dma address and length. They are obtained via
+ * sg_dma_{address,length}.
+ */
+int arm_iommu_map_sg(struct device *dev, struct scatterlist *sg, int nents,
+                    enum dma_data_direction dir, struct dma_attrs *attrs)
+{
+       struct scatterlist *s = sg, *dma = sg, *start = sg;
+       int i, count = 0;
+       unsigned int offset = s->offset;
+       unsigned int size = s->offset + s->length;
+       unsigned int max = dma_get_max_seg_size(dev);
+
+       for (i = 1; i < nents; i++) {
+               s = sg_next(s);
+
+               s->dma_address = DMA_ERROR_CODE;
+               s->dma_length = 0;
+
+               if (s->offset || (size & ~PAGE_MASK) || size + s->length > max) {
+                       if (__map_sg_chunk(dev, start, size, &dma->dma_address,
+                           dir, attrs) < 0)
+                               goto bad_mapping;
+
+                       dma->dma_address += offset;
+                       dma->dma_length = size - offset;
+
+                       size = offset = s->offset;
+                       start = s;
+                       dma = sg_next(dma);
+                       count += 1;
+               }
+               size += s->length;
+       }
+       if (__map_sg_chunk(dev, start, size, &dma->dma_address, dir, attrs) < 0)
+               goto bad_mapping;
+
+       dma->dma_address += offset;
+       dma->dma_length = size - offset;
+
+       return count+1;
+
+bad_mapping:
+       for_each_sg(sg, s, count, i)
+               __iommu_remove_mapping(dev, sg_dma_address(s), sg_dma_len(s));
+       return 0;
+}
+
+/**
+ * arm_iommu_unmap_sg - unmap a set of SG buffers mapped by dma_map_sg
+ * @dev: valid struct device pointer
+ * @sg: list of buffers
+ * @nents: number of buffers to unmap (same as was passed to dma_map_sg)
+ * @dir: DMA transfer direction (same as was passed to dma_map_sg)
+ *
+ * Unmap a set of streaming mode DMA translations.  Again, CPU access
+ * rules concerning calls here are the same as for dma_unmap_single().
+ */
+void arm_iommu_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
+                       enum dma_data_direction dir, struct dma_attrs *attrs)
+{
+       struct scatterlist *s;
+       int i;
+
+       for_each_sg(sg, s, nents, i) {
+               if (sg_dma_len(s))
+                       __iommu_remove_mapping(dev, sg_dma_address(s),
+                                              sg_dma_len(s));
+               if (!arch_is_coherent() &&
+                   !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+                       __dma_page_dev_to_cpu(sg_page(s), s->offset,
+                                             s->length, dir);
+       }
+}
+
+/**
+ * arm_iommu_sync_sg_for_cpu
+ * @dev: valid struct device pointer
+ * @sg: list of buffers
+ * @nents: number of buffers to map (returned from dma_map_sg)
+ * @dir: DMA transfer direction (same as was passed to dma_map_sg)
+ */
+void arm_iommu_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
+                       int nents, enum dma_data_direction dir)
+{
+       struct scatterlist *s;
+       int i;
+
+       for_each_sg(sg, s, nents, i)
+               if (!arch_is_coherent())
+                       __dma_page_dev_to_cpu(sg_page(s), s->offset, s->length, dir);
+
+}
+
+/**
+ * arm_iommu_sync_sg_for_device
+ * @dev: valid struct device pointer
+ * @sg: list of buffers
+ * @nents: number of buffers to map (returned from dma_map_sg)
+ * @dir: DMA transfer direction (same as was passed to dma_map_sg)
+ */
+void arm_iommu_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
+                       int nents, enum dma_data_direction dir)
+{
+       struct scatterlist *s;
+       int i;
+
+       for_each_sg(sg, s, nents, i)
+               if (!arch_is_coherent())
+                       __dma_page_cpu_to_dev(sg_page(s), s->offset, s->length, dir);
+}
+
+
+/**
+ * arm_iommu_map_page
+ * @dev: valid struct device pointer
+ * @page: page that buffer resides in
+ * @offset: offset into page for start of buffer
+ * @size: size of buffer to map
+ * @dir: DMA transfer direction
+ *
+ * IOMMU aware version of arm_dma_map_page()
+ */
+static dma_addr_t arm_iommu_map_page(struct device *dev, struct page *page,
+            unsigned long offset, size_t size, enum dma_data_direction dir,
+            struct dma_attrs *attrs)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       dma_addr_t dma_addr;
+       int ret, len = PAGE_ALIGN(size + offset);
+
+       if (!arch_is_coherent() && !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+               __dma_page_cpu_to_dev(page, offset, size, dir);
+
+       dma_addr = __alloc_iova(mapping, len);
+       if (dma_addr == DMA_ERROR_CODE)
+               return dma_addr;
+
+       ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page), len, 0);
+       if (ret < 0)
+               goto fail;
+
+       return dma_addr + offset;
+fail:
+       __free_iova(mapping, dma_addr, len);
+       return DMA_ERROR_CODE;
+}
+
+/**
+ * arm_iommu_unmap_page
+ * @dev: valid struct device pointer
+ * @handle: DMA address of buffer
+ * @size: size of buffer (same as passed to dma_map_page)
+ * @dir: DMA transfer direction (same as passed to dma_map_page)
+ *
+ * IOMMU aware version of arm_dma_unmap_page()
+ */
+static void arm_iommu_unmap_page(struct device *dev, dma_addr_t handle,
+               size_t size, enum dma_data_direction dir,
+               struct dma_attrs *attrs)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       dma_addr_t iova = handle & PAGE_MASK;
+       struct page *page = phys_to_page(iommu_iova_to_phys(mapping->domain, iova));
+       int offset = handle & ~PAGE_MASK;
+       int len = PAGE_ALIGN(size + offset);
+
+       if (!iova)
+               return;
+
+       if (!arch_is_coherent() && !dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs))
+               __dma_page_dev_to_cpu(page, offset, size, dir);
+
+       iommu_unmap(mapping->domain, iova, len);
+       __free_iova(mapping, iova, len);
+}
+
+static void arm_iommu_sync_single_for_cpu(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       dma_addr_t iova = handle & PAGE_MASK;
+       struct page *page = phys_to_page(iommu_iova_to_phys(mapping->domain, iova));
+       unsigned int offset = handle & ~PAGE_MASK;
+
+       if (!iova)
+               return;
+
+       if (!arch_is_coherent())
+               __dma_page_dev_to_cpu(page, offset, size, dir);
+}
+
+static void arm_iommu_sync_single_for_device(struct device *dev,
+               dma_addr_t handle, size_t size, enum dma_data_direction dir)
+{
+       struct dma_iommu_mapping *mapping = dev->archdata.mapping;
+       dma_addr_t iova = handle & PAGE_MASK;
+       struct page *page = phys_to_page(iommu_iova_to_phys(mapping->domain, iova));
+       unsigned int offset = handle & ~PAGE_MASK;
+
+       if (!iova)
+               return;
+
+       __dma_page_cpu_to_dev(page, offset, size, dir);
+}
+
+struct dma_map_ops iommu_ops = {
+       .alloc          = arm_iommu_alloc_attrs,
+       .free           = arm_iommu_free_attrs,
+       .mmap           = arm_iommu_mmap_attrs,
+       .get_sgtable    = arm_iommu_get_sgtable,
+
+       .map_page               = arm_iommu_map_page,
+       .unmap_page             = arm_iommu_unmap_page,
+       .sync_single_for_cpu    = arm_iommu_sync_single_for_cpu,
+       .sync_single_for_device = arm_iommu_sync_single_for_device,
+
+       .map_sg                 = arm_iommu_map_sg,
+       .unmap_sg               = arm_iommu_unmap_sg,
+       .sync_sg_for_cpu        = arm_iommu_sync_sg_for_cpu,
+       .sync_sg_for_device     = arm_iommu_sync_sg_for_device,
+};
+
+/**
+ * arm_iommu_create_mapping
+ * @bus: pointer to the bus holding the client device (for IOMMU calls)
+ * @base: start address of the valid IO address space
+ * @size: size of the valid IO address space
+ * @order: accuracy of the IO addresses allocations
+ *
+ * Creates a mapping structure which holds information about used/unused
+ * IO address ranges, which is required to perform memory allocation and
+ * mapping with IOMMU aware functions.
+ *
+ * The client device need to be attached to the mapping with
+ * arm_iommu_attach_device function.
+ */
+struct dma_iommu_mapping *
+arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size,
+                        int order)
+{
+       unsigned int count = size >> (PAGE_SHIFT + order);
+       unsigned int bitmap_size = BITS_TO_LONGS(count) * sizeof(long);
+       struct dma_iommu_mapping *mapping;
+       int err = -ENOMEM;
+
+       if (!count)
+               return ERR_PTR(-EINVAL);
+
+       mapping = kzalloc(sizeof(struct dma_iommu_mapping), GFP_KERNEL);
+       if (!mapping)
+               goto err;
+
+       mapping->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+       if (!mapping->bitmap)
+               goto err2;
+
+       mapping->base = base;
+       mapping->bits = BITS_PER_BYTE * bitmap_size;
+       mapping->order = order;
+       spin_lock_init(&mapping->lock);
+
+       mapping->domain = iommu_domain_alloc(bus);
+       if (!mapping->domain)
+               goto err3;
+
+       kref_init(&mapping->kref);
+       return mapping;
+err3:
+       kfree(mapping->bitmap);
+err2:
+       kfree(mapping);
+err:
+       return ERR_PTR(err);
+}
+
+static void release_iommu_mapping(struct kref *kref)
+{
+       struct dma_iommu_mapping *mapping =
+               container_of(kref, struct dma_iommu_mapping, kref);
+
+       iommu_domain_free(mapping->domain);
+       kfree(mapping->bitmap);
+       kfree(mapping);
+}
+
+void arm_iommu_release_mapping(struct dma_iommu_mapping *mapping)
+{
+       if (mapping)
+               kref_put(&mapping->kref, release_iommu_mapping);
+}
+
+/**
+ * arm_iommu_attach_device
+ * @dev: valid struct device pointer
+ * @mapping: io address space mapping structure (returned from
+ *     arm_iommu_create_mapping)
+ *
+ * Attaches specified io address space mapping to the provided device,
+ * this replaces the dma operations (dma_map_ops pointer) with the
+ * IOMMU aware version. More than one client might be attached to
+ * the same io address space mapping.
+ */
+int arm_iommu_attach_device(struct device *dev,
+                           struct dma_iommu_mapping *mapping)
+{
+       int err;
+
+       err = iommu_attach_device(mapping->domain, dev);
+       if (err)
+               return err;
+
+       kref_get(&mapping->kref);
+       dev->archdata.mapping = mapping;
+       set_dma_ops(dev, &iommu_ops);
+
+       pr_info("Attached IOMMU controller to %s device.\n", dev_name(dev));
+       return 0;
+}
+
+#endif
index 8f5813b..c21d06c 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/highmem.h>
 #include <linux/gfp.h>
 #include <linux/memblock.h>
+#include <linux/dma-contiguous.h>
 
 #include <asm/mach-types.h>
 #include <asm/memblock.h>
@@ -226,6 +227,17 @@ static void __init arm_adjust_dma_zone(unsigned long *size, unsigned long *hole,
 }
 #endif
 
+void __init setup_dma_zone(struct machine_desc *mdesc)
+{
+#ifdef CONFIG_ZONE_DMA
+       if (mdesc->dma_zone_size) {
+               arm_dma_zone_size = mdesc->dma_zone_size;
+               arm_dma_limit = PHYS_OFFSET + arm_dma_zone_size - 1;
+       } else
+               arm_dma_limit = 0xffffffff;
+#endif
+}
+
 static void __init arm_bootmem_free(unsigned long min, unsigned long max_low,
        unsigned long max_high)
 {
@@ -273,12 +285,9 @@ static void __init arm_bootmem_free(unsigned long min, unsigned long max_low,
         * Adjust the sizes according to any special requirements for
         * this machine type.
         */
-       if (arm_dma_zone_size) {
+       if (arm_dma_zone_size)
                arm_adjust_dma_zone(zone_size, zhole_size,
                        arm_dma_zone_size >> PAGE_SHIFT);
-               arm_dma_limit = PHYS_OFFSET + arm_dma_zone_size - 1;
-       } else
-               arm_dma_limit = 0xffffffff;
 #endif
 
        free_area_init_node(0, zone_size, min, zhole_size);
@@ -364,6 +373,12 @@ void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
        if (mdesc->reserve)
                mdesc->reserve();
 
+       /*
+        * reserve memory for DMA contigouos allocations,
+        * must come from DMA area inside low memory
+        */
+       dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));
+
        arm_memblock_steal_permitted = false;
        memblock_allow_resize();
        memblock_dump_all();
index 27f4a61..93dc0c1 100644 (file)
@@ -67,5 +67,8 @@ extern u32 arm_dma_limit;
 #define arm_dma_limit ((u32)~0)
 #endif
 
+extern phys_addr_t arm_lowmem_limit;
+
 void __init bootmem_init(void);
 void arm_mm_memblock_reserve(void);
+void dma_contiguous_remap(void);
index aa78de8..e5dad60 100644 (file)
@@ -288,6 +288,11 @@ static struct mem_type mem_types[] = {
                                PMD_SECT_UNCACHED | PMD_SECT_XN,
                .domain    = DOMAIN_KERNEL,
        },
+       [MT_MEMORY_DMA_READY] = {
+               .prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY,
+               .prot_l1   = PMD_TYPE_TABLE,
+               .domain    = DOMAIN_KERNEL,
+       },
 };
 
 const struct mem_type *get_mem_type(unsigned int type)
@@ -429,6 +434,7 @@ static void __init build_mem_type_table(void)
        if (arch_is_coherent() && cpu_is_xsc3()) {
                mem_types[MT_MEMORY].prot_sect |= PMD_SECT_S;
                mem_types[MT_MEMORY].prot_pte |= L_PTE_SHARED;
+               mem_types[MT_MEMORY_DMA_READY].prot_pte |= L_PTE_SHARED;
                mem_types[MT_MEMORY_NONCACHED].prot_sect |= PMD_SECT_S;
                mem_types[MT_MEMORY_NONCACHED].prot_pte |= L_PTE_SHARED;
        }
@@ -460,6 +466,7 @@ static void __init build_mem_type_table(void)
                        mem_types[MT_DEVICE_CACHED].prot_pte |= L_PTE_SHARED;
                        mem_types[MT_MEMORY].prot_sect |= PMD_SECT_S;
                        mem_types[MT_MEMORY].prot_pte |= L_PTE_SHARED;
+                       mem_types[MT_MEMORY_DMA_READY].prot_pte |= L_PTE_SHARED;
                        mem_types[MT_MEMORY_NONCACHED].prot_sect |= PMD_SECT_S;
                        mem_types[MT_MEMORY_NONCACHED].prot_pte |= L_PTE_SHARED;
                }
@@ -512,6 +519,7 @@ static void __init build_mem_type_table(void)
        mem_types[MT_HIGH_VECTORS].prot_l1 |= ecc_mask;
        mem_types[MT_MEMORY].prot_sect |= ecc_mask | cp->pmd;
        mem_types[MT_MEMORY].prot_pte |= kern_pgprot;
+       mem_types[MT_MEMORY_DMA_READY].prot_pte |= kern_pgprot;
        mem_types[MT_MEMORY_NONCACHED].prot_sect |= ecc_mask;
        mem_types[MT_ROM].prot_sect |= cp->pmd;
 
@@ -596,7 +604,7 @@ static void __init alloc_init_section(pud_t *pud, unsigned long addr,
         * L1 entries, whereas PGDs refer to a group of L1 entries making
         * up one logical pointer to an L2 table.
         */
-       if (((addr | end | phys) & ~SECTION_MASK) == 0) {
+       if (type->prot_sect && ((addr | end | phys) & ~SECTION_MASK) == 0) {
                pmd_t *p = pmd;
 
 #ifndef CONFIG_ARM_LPAE
@@ -814,7 +822,7 @@ static int __init early_vmalloc(char *arg)
 }
 early_param("vmalloc", early_vmalloc);
 
-static phys_addr_t lowmem_limit __initdata = 0;
+phys_addr_t arm_lowmem_limit __initdata = 0;
 
 void __init sanity_check_meminfo(void)
 {
@@ -897,8 +905,8 @@ void __init sanity_check_meminfo(void)
                        bank->size = newsize;
                }
 #endif
-               if (!bank->highmem && bank->start + bank->size > lowmem_limit)
-                       lowmem_limit = bank->start + bank->size;
+               if (!bank->highmem && bank->start + bank->size > arm_lowmem_limit)
+                       arm_lowmem_limit = bank->start + bank->size;
 
                j++;
        }
@@ -923,8 +931,8 @@ void __init sanity_check_meminfo(void)
        }
 #endif
        meminfo.nr_banks = j;
-       high_memory = __va(lowmem_limit - 1) + 1;
-       memblock_set_current_limit(lowmem_limit);
+       high_memory = __va(arm_lowmem_limit - 1) + 1;
+       memblock_set_current_limit(arm_lowmem_limit);
 }
 
 static inline void prepare_page_table(void)
@@ -949,8 +957,8 @@ static inline void prepare_page_table(void)
         * Find the end of the first block of lowmem.
         */
        end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
-       if (end >= lowmem_limit)
-               end = lowmem_limit;
+       if (end >= arm_lowmem_limit)
+               end = arm_lowmem_limit;
 
        /*
         * Clear out all the kernel space mappings, except for the first
@@ -1093,8 +1101,8 @@ static void __init map_lowmem(void)
                phys_addr_t end = start + reg->size;
                struct map_desc map;
 
-               if (end > lowmem_limit)
-                       end = lowmem_limit;
+               if (end > arm_lowmem_limit)
+                       end = arm_lowmem_limit;
                if (start >= end)
                        break;
 
@@ -1115,11 +1123,12 @@ void __init paging_init(struct machine_desc *mdesc)
 {
        void *zero_page;
 
-       memblock_set_current_limit(lowmem_limit);
+       memblock_set_current_limit(arm_lowmem_limit);
 
        build_mem_type_table();
        prepare_page_table();
        map_lowmem();
+       dma_contiguous_remap();
        devicemaps_init(mdesc);
        kmap_init();
 
index 162be66..bf312c3 100644 (file)
@@ -17,7 +17,7 @@ struct arm_vmregion {
        struct list_head        vm_list;
        unsigned long           vm_start;
        unsigned long           vm_end;
-       struct page             *vm_pages;
+       void                    *priv;
        int                     vm_active;
        const void              *caller;
 };
index c9866b0..7cbdfda 100644 (file)
@@ -31,6 +31,7 @@ config X86
        select ARCH_WANT_OPTIONAL_GPIOLIB
        select ARCH_WANT_FRAME_POINTERS
        select HAVE_DMA_ATTRS
+       select HAVE_DMA_CONTIGUOUS if !SWIOTLB
        select HAVE_KRETPROBES
        select HAVE_OPTPROBES
        select HAVE_FTRACE_MCOUNT_RECORD
diff --git a/arch/x86/include/asm/dma-contiguous.h b/arch/x86/include/asm/dma-contiguous.h
new file mode 100644 (file)
index 0000000..c092416
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef ASMX86_DMA_CONTIGUOUS_H
+#define ASMX86_DMA_CONTIGUOUS_H
+
+#ifdef __KERNEL__
+
+#include <linux/types.h>
+#include <asm-generic/dma-contiguous.h>
+
+static inline void
+dma_contiguous_early_fixup(phys_addr_t base, unsigned long size) { }
+
+#endif
+#endif
index 4b4331d..7b9227b 100644 (file)
@@ -13,6 +13,7 @@
 #include <asm/io.h>
 #include <asm/swiotlb.h>
 #include <asm-generic/dma-coherent.h>
+#include <linux/dma-contiguous.h>
 
 #ifdef CONFIG_ISA
 # define ISA_DMA_BIT_MASK DMA_BIT_MASK(24)
@@ -62,6 +63,10 @@ extern void *dma_generic_alloc_coherent(struct device *dev, size_t size,
                                        dma_addr_t *dma_addr, gfp_t flag,
                                        struct dma_attrs *attrs);
 
+extern void dma_generic_free_coherent(struct device *dev, size_t size,
+                                     void *vaddr, dma_addr_t dma_addr,
+                                     struct dma_attrs *attrs);
+
 static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size)
 {
        if (!dev->dma_mask)
index 3003250..62c9457 100644 (file)
@@ -100,14 +100,18 @@ void *dma_generic_alloc_coherent(struct device *dev, size_t size,
                                 struct dma_attrs *attrs)
 {
        unsigned long dma_mask;
-       struct page *page;
+       struct page *page = NULL;
+       unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
        dma_addr_t addr;
 
        dma_mask = dma_alloc_coherent_mask(dev, flag);
 
        flag |= __GFP_ZERO;
 again:
-       page = alloc_pages_node(dev_to_node(dev), flag, get_order(size));
+       if (!(flag & GFP_ATOMIC))
+               page = dma_alloc_from_contiguous(dev, count, get_order(size));
+       if (!page)
+               page = alloc_pages_node(dev_to_node(dev), flag, get_order(size));
        if (!page)
                return NULL;
 
@@ -127,6 +131,16 @@ again:
        return page_address(page);
 }
 
+void dma_generic_free_coherent(struct device *dev, size_t size, void *vaddr,
+                              dma_addr_t dma_addr, struct dma_attrs *attrs)
+{
+       unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+       struct page *page = virt_to_page(vaddr);
+
+       if (!dma_release_from_contiguous(dev, page, count))
+               free_pages((unsigned long)vaddr, get_order(size));
+}
+
 /*
  * See <Documentation/x86/x86_64/boot-options.txt> for the iommu kernel
  * parameter documentation.
index f960506..871be4a 100644 (file)
@@ -74,12 +74,6 @@ static int nommu_map_sg(struct device *hwdev, struct scatterlist *sg,
        return nents;
 }
 
-static void nommu_free_coherent(struct device *dev, size_t size, void *vaddr,
-                               dma_addr_t dma_addr, struct dma_attrs *attrs)
-{
-       free_pages((unsigned long)vaddr, get_order(size));
-}
-
 static void nommu_sync_single_for_device(struct device *dev,
                        dma_addr_t addr, size_t size,
                        enum dma_data_direction dir)
@@ -97,7 +91,7 @@ static void nommu_sync_sg_for_device(struct device *dev,
 
 struct dma_map_ops nommu_dma_ops = {
        .alloc                  = dma_generic_alloc_coherent,
-       .free                   = nommu_free_coherent,
+       .free                   = dma_generic_free_coherent,
        .map_sg                 = nommu_map_sg,
        .map_page               = nommu_map_page,
        .sync_single_for_device = nommu_sync_single_for_device,
index 1a29015..d6c956e 100644 (file)
@@ -50,6 +50,7 @@
 #include <asm/pci-direct.h>
 #include <linux/init_ohci1394_dma.h>
 #include <linux/kvm_para.h>
+#include <linux/dma-contiguous.h>
 
 #include <linux/errno.h>
 #include <linux/kernel.h>
@@ -934,6 +935,7 @@ void __init setup_arch(char **cmdline_p)
        }
 #endif
        memblock.current_limit = get_max_mapped();
+       dma_contiguous_reserve(0);
 
        /*
         * NOTE: On x86-32, only from this point on, fixmaps are ready for use.
index 9aa618a..9b21469 100644 (file)
@@ -192,4 +192,93 @@ config DMA_SHARED_BUFFER
          APIs extension; the file's descriptor can then be passed on to other
          driver.
 
+config CMA
+       bool "Contiguous Memory Allocator (EXPERIMENTAL)"
+       depends on HAVE_DMA_CONTIGUOUS && HAVE_MEMBLOCK && EXPERIMENTAL
+       select MIGRATION
+       help
+         This enables the Contiguous Memory Allocator which allows drivers
+         to allocate big physically-contiguous blocks of memory for use with
+         hardware components that do not support I/O map nor scatter-gather.
+
+         For more information see <include/linux/dma-contiguous.h>.
+         If unsure, say "n".
+
+if CMA
+
+config CMA_DEBUG
+       bool "CMA debug messages (DEVELOPMENT)"
+       depends on DEBUG_KERNEL
+       help
+         Turns on debug messages in CMA.  This produces KERN_DEBUG
+         messages for every CMA call as well as various messages while
+         processing calls such as dma_alloc_from_contiguous().
+         This option does not affect warning and error messages.
+
+comment "Default contiguous memory area size:"
+
+config CMA_SIZE_MBYTES
+       int "Size in Mega Bytes"
+       depends on !CMA_SIZE_SEL_PERCENTAGE
+       default 16
+       help
+         Defines the size (in MiB) of the default memory area for Contiguous
+         Memory Allocator.
+
+config CMA_SIZE_PERCENTAGE
+       int "Percentage of total memory"
+       depends on !CMA_SIZE_SEL_MBYTES
+       default 10
+       help
+         Defines the size of the default memory area for Contiguous Memory
+         Allocator as a percentage of the total memory in the system.
+
+choice
+       prompt "Selected region size"
+       default CMA_SIZE_SEL_ABSOLUTE
+
+config CMA_SIZE_SEL_MBYTES
+       bool "Use mega bytes value only"
+
+config CMA_SIZE_SEL_PERCENTAGE
+       bool "Use percentage value only"
+
+config CMA_SIZE_SEL_MIN
+       bool "Use lower value (minimum)"
+
+config CMA_SIZE_SEL_MAX
+       bool "Use higher value (maximum)"
+
+endchoice
+
+config CMA_ALIGNMENT
+       int "Maximum PAGE_SIZE order of alignment for contiguous buffers"
+       range 4 9
+       default 8
+       help
+         DMA mapping framework by default aligns all buffers to the smallest
+         PAGE_SIZE order which is greater than or equal to the requested buffer
+         size. This works well for buffers up to a few hundreds kilobytes, but
+         for larger buffers it just a memory waste. With this parameter you can
+         specify the maximum PAGE_SIZE order for contiguous buffers. Larger
+         buffers will be aligned only to this specified order. The order is
+         expressed as a power of two multiplied by the PAGE_SIZE.
+
+         For example, if your system defaults to 4KiB pages, the order value
+         of 8 means that the buffers will be aligned up to 1MiB only.
+
+         If unsure, leave the default value "8".
+
+config CMA_AREAS
+       int "Maximum count of the CMA device-private areas"
+       default 7
+       help
+         CMA allows to create CMA areas for particular devices. This parameter
+         sets the maximum number of such device private CMA areas in the
+         system.
+
+         If unsure, leave the default value "7".
+
+endif
+
 endmenu
index b6d1b9c..5aa2d70 100644 (file)
@@ -6,6 +6,7 @@ obj-y                   := core.o bus.o dd.o syscore.o \
                           attribute_container.o transport_class.o \
                           topology.o
 obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
+obj-$(CONFIG_CMA) += dma-contiguous.o
 obj-y                  += power/
 obj-$(CONFIG_HAS_DMA)  += dma-mapping.o
 obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
index 07cbbc6..20258e1 100644 (file)
@@ -44,8 +44,26 @@ static int dma_buf_release(struct inode *inode, struct file *file)
        return 0;
 }
 
+static int dma_buf_mmap_internal(struct file *file, struct vm_area_struct *vma)
+{
+       struct dma_buf *dmabuf;
+
+       if (!is_dma_buf_file(file))
+               return -EINVAL;
+
+       dmabuf = file->private_data;
+
+       /* check for overflowing the buffer's size */
+       if (vma->vm_pgoff + ((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) >
+           dmabuf->size >> PAGE_SHIFT)
+               return -EINVAL;
+
+       return dmabuf->ops->mmap(dmabuf, vma);
+}
+
 static const struct file_operations dma_buf_fops = {
        .release        = dma_buf_release,
+       .mmap           = dma_buf_mmap_internal,
 };
 
 /*
@@ -82,7 +100,8 @@ struct dma_buf *dma_buf_export(void *priv, const struct dma_buf_ops *ops,
                          || !ops->unmap_dma_buf
                          || !ops->release
                          || !ops->kmap_atomic
-                         || !ops->kmap)) {
+                         || !ops->kmap
+                         || !ops->mmap)) {
                return ERR_PTR(-EINVAL);
        }
 
@@ -406,3 +425,81 @@ void dma_buf_kunmap(struct dma_buf *dmabuf, unsigned long page_num,
                dmabuf->ops->kunmap(dmabuf, page_num, vaddr);
 }
 EXPORT_SYMBOL_GPL(dma_buf_kunmap);
+
+
+/**
+ * dma_buf_mmap - Setup up a userspace mmap with the given vma
+ * @dmabuf:    [in]    buffer that should back the vma
+ * @vma:       [in]    vma for the mmap
+ * @pgoff:     [in]    offset in pages where this mmap should start within the
+ *                     dma-buf buffer.
+ *
+ * This function adjusts the passed in vma so that it points at the file of the
+ * dma_buf operation. It alsog adjusts the starting pgoff and does bounds
+ * checking on the size of the vma. Then it calls the exporters mmap function to
+ * set up the mapping.
+ *
+ * Can return negative error values, returns 0 on success.
+ */
+int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma,
+                unsigned long pgoff)
+{
+       if (WARN_ON(!dmabuf || !vma))
+               return -EINVAL;
+
+       /* check for offset overflow */
+       if (pgoff + ((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) < pgoff)
+               return -EOVERFLOW;
+
+       /* check for overflowing the buffer's size */
+       if (pgoff + ((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) >
+           dmabuf->size >> PAGE_SHIFT)
+               return -EINVAL;
+
+       /* readjust the vma */
+       if (vma->vm_file)
+               fput(vma->vm_file);
+
+       vma->vm_file = dmabuf->file;
+       get_file(vma->vm_file);
+
+       vma->vm_pgoff = pgoff;
+
+       return dmabuf->ops->mmap(dmabuf, vma);
+}
+EXPORT_SYMBOL_GPL(dma_buf_mmap);
+
+/**
+ * dma_buf_vmap - Create virtual mapping for the buffer object into kernel
+ * address space. Same restrictions as for vmap and friends apply.
+ * @dmabuf:    [in]    buffer to vmap
+ *
+ * This call may fail due to lack of virtual mapping address space.
+ * These calls are optional in drivers. The intended use for them
+ * is for mapping objects linear in kernel space for high use objects.
+ * Please attempt to use kmap/kunmap before thinking about these interfaces.
+ */
+void *dma_buf_vmap(struct dma_buf *dmabuf)
+{
+       if (WARN_ON(!dmabuf))
+               return NULL;
+
+       if (dmabuf->ops->vmap)
+               return dmabuf->ops->vmap(dmabuf);
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(dma_buf_vmap);
+
+/**
+ * dma_buf_vunmap - Unmap a vmap obtained by dma_buf_vmap.
+ * @dmabuf:    [in]    buffer to vunmap
+ */
+void dma_buf_vunmap(struct dma_buf *dmabuf, void *vaddr)
+{
+       if (WARN_ON(!dmabuf))
+               return;
+
+       if (dmabuf->ops->vunmap)
+               dmabuf->ops->vunmap(dmabuf, vaddr);
+}
+EXPORT_SYMBOL_GPL(dma_buf_vunmap);
index bb0025c..1b85949 100644 (file)
@@ -10,6 +10,7 @@
 struct dma_coherent_mem {
        void            *virt_base;
        dma_addr_t      device_base;
+       phys_addr_t     pfn_base;
        int             size;
        int             flags;
        unsigned long   *bitmap;
@@ -44,6 +45,7 @@ int dma_declare_coherent_memory(struct device *dev, dma_addr_t bus_addr,
 
        dev->dma_mem->virt_base = mem_base;
        dev->dma_mem->device_base = device_addr;
+       dev->dma_mem->pfn_base = PFN_DOWN(bus_addr);
        dev->dma_mem->size = pages;
        dev->dma_mem->flags = flags;
 
@@ -176,3 +178,43 @@ int dma_release_from_coherent(struct device *dev, int order, void *vaddr)
        return 0;
 }
 EXPORT_SYMBOL(dma_release_from_coherent);
+
+/**
+ * dma_mmap_from_coherent() - try to mmap the memory allocated from
+ * per-device coherent memory pool to userspace
+ * @dev:       device from which the memory was allocated
+ * @vma:       vm_area for the userspace memory
+ * @vaddr:     cpu address returned by dma_alloc_from_coherent
+ * @size:      size of the memory buffer allocated by dma_alloc_from_coherent
+ *
+ * This checks whether the memory was allocated from the per-device
+ * coherent memory pool and if so, maps that memory to the provided vma.
+ *
+ * Returns 1 if we correctly mapped the memory, or 0 if
+ * dma_release_coherent() should proceed with mapping memory from
+ * generic pools.
+ */
+int dma_mmap_from_coherent(struct device *dev, struct vm_area_struct *vma,
+                          void *vaddr, size_t size, int *ret)
+{
+       struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;
+
+       if (mem && vaddr >= mem->virt_base && vaddr + size <=
+                  (mem->virt_base + (mem->size << PAGE_SHIFT))) {
+               unsigned long off = vma->vm_pgoff;
+               int start = (vaddr - mem->virt_base) >> PAGE_SHIFT;
+               int user_count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+               int count = size >> PAGE_SHIFT;
+
+               *ret = -ENXIO;
+               if (off < count && user_count <= count - off) {
+                       unsigned pfn = mem->pfn_base + start + off;
+                       *ret = remap_pfn_range(vma, vma->vm_start, pfn,
+                                              user_count << PAGE_SHIFT,
+                                              vma->vm_page_prot);
+               }
+               return 1;
+       }
+       return 0;
+}
+EXPORT_SYMBOL(dma_mmap_from_coherent);
diff --git a/drivers/base/dma-contiguous.c b/drivers/base/dma-contiguous.c
new file mode 100644 (file)
index 0000000..78efb03
--- /dev/null
@@ -0,0 +1,401 @@
+/*
+ * Contiguous Memory Allocator for DMA mapping framework
+ * Copyright (c) 2010-2011 by Samsung Electronics.
+ * Written by:
+ *     Marek Szyprowski <m.szyprowski@samsung.com>
+ *     Michal Nazarewicz <mina86@mina86.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License or (at your optional) any later version of the license.
+ */
+
+#define pr_fmt(fmt) "cma: " fmt
+
+#ifdef CONFIG_CMA_DEBUG
+#ifndef DEBUG
+#  define DEBUG
+#endif
+#endif
+
+#include <asm/page.h>
+#include <asm/dma-contiguous.h>
+
+#include <linux/memblock.h>
+#include <linux/err.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/page-isolation.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+#include <linux/mm_types.h>
+#include <linux/dma-contiguous.h>
+
+#ifndef SZ_1M
+#define SZ_1M (1 << 20)
+#endif
+
+struct cma {
+       unsigned long   base_pfn;
+       unsigned long   count;
+       unsigned long   *bitmap;
+};
+
+struct cma *dma_contiguous_default_area;
+
+#ifdef CONFIG_CMA_SIZE_MBYTES
+#define CMA_SIZE_MBYTES CONFIG_CMA_SIZE_MBYTES
+#else
+#define CMA_SIZE_MBYTES 0
+#endif
+
+/*
+ * Default global CMA area size can be defined in kernel's .config.
+ * This is usefull mainly for distro maintainers to create a kernel
+ * that works correctly for most supported systems.
+ * The size can be set in bytes or as a percentage of the total memory
+ * in the system.
+ *
+ * Users, who want to set the size of global CMA area for their system
+ * should use cma= kernel parameter.
+ */
+static const unsigned long size_bytes = CMA_SIZE_MBYTES * SZ_1M;
+static long size_cmdline = -1;
+
+static int __init early_cma(char *p)
+{
+       pr_debug("%s(%s)\n", __func__, p);
+       size_cmdline = memparse(p, &p);
+       return 0;
+}
+early_param("cma", early_cma);
+
+#ifdef CONFIG_CMA_SIZE_PERCENTAGE
+
+static unsigned long __init __maybe_unused cma_early_percent_memory(void)
+{
+       struct memblock_region *reg;
+       unsigned long total_pages = 0;
+
+       /*
+        * We cannot use memblock_phys_mem_size() here, because
+        * memblock_analyze() has not been called yet.
+        */
+       for_each_memblock(memory, reg)
+               total_pages += memblock_region_memory_end_pfn(reg) -
+                              memblock_region_memory_base_pfn(reg);
+
+       return (total_pages * CONFIG_CMA_SIZE_PERCENTAGE / 100) << PAGE_SHIFT;
+}
+
+#else
+
+static inline __maybe_unused unsigned long cma_early_percent_memory(void)
+{
+       return 0;
+}
+
+#endif
+
+/**
+ * dma_contiguous_reserve() - reserve area for contiguous memory handling
+ * @limit: End address of the reserved memory (optional, 0 for any).
+ *
+ * This function reserves memory from early allocator. It should be
+ * called by arch specific code once the early allocator (memblock or bootmem)
+ * has been activated and all other subsystems have already allocated/reserved
+ * memory.
+ */
+void __init dma_contiguous_reserve(phys_addr_t limit)
+{
+       unsigned long selected_size = 0;
+
+       pr_debug("%s(limit %08lx)\n", __func__, (unsigned long)limit);
+
+       if (size_cmdline != -1) {
+               selected_size = size_cmdline;
+       } else {
+#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
+               selected_size = size_bytes;
+#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
+               selected_size = cma_early_percent_memory();
+#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
+               selected_size = min(size_bytes, cma_early_percent_memory());
+#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
+               selected_size = max(size_bytes, cma_early_percent_memory());
+#endif
+       }
+
+       if (selected_size) {
+               pr_debug("%s: reserving %ld MiB for global area\n", __func__,
+                        selected_size / SZ_1M);
+
+               dma_declare_contiguous(NULL, selected_size, 0, limit);
+       }
+};
+
+static DEFINE_MUTEX(cma_mutex);
+
+static __init int cma_activate_area(unsigned long base_pfn, unsigned long count)
+{
+       unsigned long pfn = base_pfn;
+       unsigned i = count >> pageblock_order;
+       struct zone *zone;
+
+       WARN_ON_ONCE(!pfn_valid(pfn));
+       zone = page_zone(pfn_to_page(pfn));
+
+       do {
+               unsigned j;
+               base_pfn = pfn;
+               for (j = pageblock_nr_pages; j; --j, pfn++) {
+                       WARN_ON_ONCE(!pfn_valid(pfn));
+                       if (page_zone(pfn_to_page(pfn)) != zone)
+                               return -EINVAL;
+               }
+               init_cma_reserved_pageblock(pfn_to_page(base_pfn));
+       } while (--i);
+       return 0;
+}
+
+static __init struct cma *cma_create_area(unsigned long base_pfn,
+                                    unsigned long count)
+{
+       int bitmap_size = BITS_TO_LONGS(count) * sizeof(long);
+       struct cma *cma;
+       int ret = -ENOMEM;
+
+       pr_debug("%s(base %08lx, count %lx)\n", __func__, base_pfn, count);
+
+       cma = kmalloc(sizeof *cma, GFP_KERNEL);
+       if (!cma)
+               return ERR_PTR(-ENOMEM);
+
+       cma->base_pfn = base_pfn;
+       cma->count = count;
+       cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+
+       if (!cma->bitmap)
+               goto no_mem;
+
+       ret = cma_activate_area(base_pfn, count);
+       if (ret)
+               goto error;
+
+       pr_debug("%s: returned %p\n", __func__, (void *)cma);
+       return cma;
+
+error:
+       kfree(cma->bitmap);
+no_mem:
+       kfree(cma);
+       return ERR_PTR(ret);
+}
+
+static struct cma_reserved {
+       phys_addr_t start;
+       unsigned long size;
+       struct device *dev;
+} cma_reserved[MAX_CMA_AREAS] __initdata;
+static unsigned cma_reserved_count __initdata;
+
+static int __init cma_init_reserved_areas(void)
+{
+       struct cma_reserved *r = cma_reserved;
+       unsigned i = cma_reserved_count;
+
+       pr_debug("%s()\n", __func__);
+
+       for (; i; --i, ++r) {
+               struct cma *cma;
+               cma = cma_create_area(PFN_DOWN(r->start),
+                                     r->size >> PAGE_SHIFT);
+               if (!IS_ERR(cma))
+                       dev_set_cma_area(r->dev, cma);
+       }
+       return 0;
+}
+core_initcall(cma_init_reserved_areas);
+
+/**
+ * dma_declare_contiguous() - reserve area for contiguous memory handling
+ *                           for particular device
+ * @dev:   Pointer to device structure.
+ * @size:  Size of the reserved memory.
+ * @base:  Start address of the reserved memory (optional, 0 for any).
+ * @limit: End address of the reserved memory (optional, 0 for any).
+ *
+ * This function reserves memory for specified device. It should be
+ * called by board specific code when early allocator (memblock or bootmem)
+ * is still activate.
+ */
+int __init dma_declare_contiguous(struct device *dev, unsigned long size,
+                                 phys_addr_t base, phys_addr_t limit)
+{
+       struct cma_reserved *r = &cma_reserved[cma_reserved_count];
+       unsigned long alignment;
+
+       pr_debug("%s(size %lx, base %08lx, limit %08lx)\n", __func__,
+                (unsigned long)size, (unsigned long)base,
+                (unsigned long)limit);
+
+       /* Sanity checks */
+       if (cma_reserved_count == ARRAY_SIZE(cma_reserved)) {
+               pr_err("Not enough slots for CMA reserved regions!\n");
+               return -ENOSPC;
+       }
+
+       if (!size)
+               return -EINVAL;
+
+       /* Sanitise input arguments */
+       alignment = PAGE_SIZE << max(MAX_ORDER, pageblock_order);
+       base = ALIGN(base, alignment);
+       size = ALIGN(size, alignment);
+       limit &= ~(alignment - 1);
+
+       /* Reserve memory */
+       if (base) {
+               if (memblock_is_region_reserved(base, size) ||
+                   memblock_reserve(base, size) < 0) {
+                       base = -EBUSY;
+                       goto err;
+               }
+       } else {
+               /*
+                * Use __memblock_alloc_base() since
+                * memblock_alloc_base() panic()s.
+                */
+               phys_addr_t addr = __memblock_alloc_base(size, alignment, limit);
+               if (!addr) {
+                       base = -ENOMEM;
+                       goto err;
+               } else if (addr + size > ~(unsigned long)0) {
+                       memblock_free(addr, size);
+                       base = -EINVAL;
+                       goto err;
+               } else {
+                       base = addr;
+               }
+       }
+
+       /*
+        * Each reserved area must be initialised later, when more kernel
+        * subsystems (like slab allocator) are available.
+        */
+       r->start = base;
+       r->size = size;
+       r->dev = dev;
+       cma_reserved_count++;
+       pr_info("CMA: reserved %ld MiB at %08lx\n", size / SZ_1M,
+               (unsigned long)base);
+
+       /* Architecture specific contiguous memory fixup. */
+       dma_contiguous_early_fixup(base, size);
+       return 0;
+err:
+       pr_err("CMA: failed to reserve %ld MiB\n", size / SZ_1M);
+       return base;
+}
+
+/**
+ * dma_alloc_from_contiguous() - allocate pages from contiguous area
+ * @dev:   Pointer to device for which the allocation is performed.
+ * @count: Requested number of pages.
+ * @align: Requested alignment of pages (in PAGE_SIZE order).
+ *
+ * This function allocates memory buffer for specified device. It uses
+ * device specific contiguous memory area if available or the default
+ * global one. Requires architecture specific get_dev_cma_area() helper
+ * function.
+ */
+struct page *dma_alloc_from_contiguous(struct device *dev, int count,
+                                      unsigned int align)
+{
+       unsigned long mask, pfn, pageno, start = 0;
+       struct cma *cma = dev_get_cma_area(dev);
+       int ret;
+
+       if (!cma || !cma->count)
+               return NULL;
+
+       if (align > CONFIG_CMA_ALIGNMENT)
+               align = CONFIG_CMA_ALIGNMENT;
+
+       pr_debug("%s(cma %p, count %d, align %d)\n", __func__, (void *)cma,
+                count, align);
+
+       if (!count)
+               return NULL;
+
+       mask = (1 << align) - 1;
+
+       mutex_lock(&cma_mutex);
+
+       for (;;) {
+               pageno = bitmap_find_next_zero_area(cma->bitmap, cma->count,
+                                                   start, count, mask);
+               if (pageno >= cma->count) {
+                       ret = -ENOMEM;
+                       goto error;
+               }
+
+               pfn = cma->base_pfn + pageno;
+               ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA);
+               if (ret == 0) {
+                       bitmap_set(cma->bitmap, pageno, count);
+                       break;
+               } else if (ret != -EBUSY) {
+                       goto error;
+               }
+               pr_debug("%s(): memory range at %p is busy, retrying\n",
+                        __func__, pfn_to_page(pfn));
+               /* try again with a bit different memory target */
+               start = pageno + mask + 1;
+       }
+
+       mutex_unlock(&cma_mutex);
+
+       pr_debug("%s(): returned %p\n", __func__, pfn_to_page(pfn));
+       return pfn_to_page(pfn);
+error:
+       mutex_unlock(&cma_mutex);
+       return NULL;
+}
+
+/**
+ * dma_release_from_contiguous() - release allocated pages
+ * @dev:   Pointer to device for which the pages were allocated.
+ * @pages: Allocated pages.
+ * @count: Number of allocated pages.
+ *
+ * This function releases memory allocated by dma_alloc_from_contiguous().
+ * It returns false when provided pages do not belong to contiguous area and
+ * true otherwise.
+ */
+bool dma_release_from_contiguous(struct device *dev, struct page *pages,
+                                int count)
+{
+       struct cma *cma = dev_get_cma_area(dev);
+       unsigned long pfn;
+
+       if (!cma || !pages)
+               return false;
+
+       pr_debug("%s(page %p)\n", __func__, (void *)pages);
+
+       pfn = page_to_pfn(pages);
+
+       if (pfn < cma->base_pfn || pfn >= cma->base_pfn + cma->count)
+               return false;
+
+       VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);
+
+       mutex_lock(&cma_mutex);
+       bitmap_clear(cma->bitmap, pfn - cma->base_pfn, count);
+       free_contig_range(pfn, count);
+       mutex_unlock(&cma_mutex);
+
+       return true;
+}
index 6f3676f..49785c1 100644 (file)
@@ -217,4 +217,22 @@ void dmam_release_declared_memory(struct device *dev)
 }
 EXPORT_SYMBOL(dmam_release_declared_memory);
 
+/*
+ * Create scatter-list for the already allocated DMA buffer.
+ */
+int dma_common_get_sgtable(struct device *dev, struct sg_table *sgt,
+                void *cpu_addr, dma_addr_t handle, size_t size)
+{
+       struct page *page = virt_to_page(cpu_addr);
+       int ret;
+
+       ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+       if (unlikely(ret))
+               return ret;
+
+       sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
+       return 0;
+}
+EXPORT_SYMBOL(dma_common_get_sgtable);
+
 #endif
index 3bd9fff..223e31e 100644 (file)
@@ -162,4 +162,26 @@ config TEGRA_IOMMU_SMMU
          space through the SMMU (System Memory Management Unit)
          hardware included on Tegra SoCs.
 
+config EXYNOS_IOMMU
+       bool "Exynos IOMMU Support"
+       depends on EXYNOS_DEV_SYSMMU
+       select IOMMU_API
+       select ARM_DMA_USE_IOMMU
+       help
+         Support for the IOMMU(System MMU) of Samsung Exynos application
+         processor family. This enables H/W multimedia accellerators to see
+         non-linear physical memory chunks as a linear memory in their
+         address spaces
+
+         If unsure, say N here.
+
+config EXYNOS_IOMMU_DEBUG
+       bool "Debugging log for Exynos IOMMU"
+       depends on EXYNOS_IOMMU
+       help
+         Select this to see the detailed log message that shows what
+         happens in the IOMMU driver
+
+         Say N unless you need kernel log message for IOMMU debugging
+
 endif # IOMMU_SUPPORT
index 7ad7a3b..d06dec6 100644 (file)
@@ -10,3 +10,4 @@ obj-$(CONFIG_OMAP_IOVMM) += omap-iovmm.o
 obj-$(CONFIG_OMAP_IOMMU_DEBUG) += omap-iommu-debug.o
 obj-$(CONFIG_TEGRA_IOMMU_GART) += tegra-gart.o
 obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o
+obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c
new file mode 100644 (file)
index 0000000..3b6ce8d
--- /dev/null
@@ -0,0 +1,1092 @@
+/* linux/drivers/iommu/exynos_iommu.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com
+ *
+ * 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.
+ */
+
+#ifdef CONFIG_EXYNOS_IOMMU_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/mm.h>
+#include <linux/iommu.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/memblock.h>
+#include <linux/export.h>
+
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+
+#include <mach/sysmmu.h>
+
+/* We does not consider super section mapping (16MB) */
+#define SECT_ORDER 20
+#define LPAGE_ORDER 16
+#define SPAGE_ORDER 12
+
+#define SECT_SIZE (1 << SECT_ORDER)
+#define LPAGE_SIZE (1 << LPAGE_ORDER)
+#define SPAGE_SIZE (1 << SPAGE_ORDER)
+
+#define SECT_MASK (~(SECT_SIZE - 1))
+#define LPAGE_MASK (~(LPAGE_SIZE - 1))
+#define SPAGE_MASK (~(SPAGE_SIZE - 1))
+
+#define lv1ent_fault(sent) (((*(sent) & 3) == 0) || ((*(sent) & 3) == 3))
+#define lv1ent_page(sent) ((*(sent) & 3) == 1)
+#define lv1ent_section(sent) ((*(sent) & 3) == 2)
+
+#define lv2ent_fault(pent) ((*(pent) & 3) == 0)
+#define lv2ent_small(pent) ((*(pent) & 2) == 2)
+#define lv2ent_large(pent) ((*(pent) & 3) == 1)
+
+#define section_phys(sent) (*(sent) & SECT_MASK)
+#define section_offs(iova) ((iova) & 0xFFFFF)
+#define lpage_phys(pent) (*(pent) & LPAGE_MASK)
+#define lpage_offs(iova) ((iova) & 0xFFFF)
+#define spage_phys(pent) (*(pent) & SPAGE_MASK)
+#define spage_offs(iova) ((iova) & 0xFFF)
+
+#define lv1ent_offset(iova) ((iova) >> SECT_ORDER)
+#define lv2ent_offset(iova) (((iova) & 0xFF000) >> SPAGE_ORDER)
+
+#define NUM_LV1ENTRIES 4096
+#define NUM_LV2ENTRIES 256
+
+#define LV2TABLE_SIZE (NUM_LV2ENTRIES * sizeof(long))
+
+#define SPAGES_PER_LPAGE (LPAGE_SIZE / SPAGE_SIZE)
+
+#define lv2table_base(sent) (*(sent) & 0xFFFFFC00)
+
+#define mk_lv1ent_sect(pa) ((pa) | 2)
+#define mk_lv1ent_page(pa) ((pa) | 1)
+#define mk_lv2ent_lpage(pa) ((pa) | 1)
+#define mk_lv2ent_spage(pa) ((pa) | 2)
+
+#define CTRL_ENABLE    0x5
+#define CTRL_BLOCK     0x7
+#define CTRL_DISABLE   0x0
+
+#define REG_MMU_CTRL           0x000
+#define REG_MMU_CFG            0x004
+#define REG_MMU_STATUS         0x008
+#define REG_MMU_FLUSH          0x00C
+#define REG_MMU_FLUSH_ENTRY    0x010
+#define REG_PT_BASE_ADDR       0x014
+#define REG_INT_STATUS         0x018
+#define REG_INT_CLEAR          0x01C
+
+#define REG_PAGE_FAULT_ADDR    0x024
+#define REG_AW_FAULT_ADDR      0x028
+#define REG_AR_FAULT_ADDR      0x02C
+#define REG_DEFAULT_SLAVE_ADDR 0x030
+
+#define REG_MMU_VERSION                0x034
+
+#define REG_PB0_SADDR          0x04C
+#define REG_PB0_EADDR          0x050
+#define REG_PB1_SADDR          0x054
+#define REG_PB1_EADDR          0x058
+
+static unsigned long *section_entry(unsigned long *pgtable, unsigned long iova)
+{
+       return pgtable + lv1ent_offset(iova);
+}
+
+static unsigned long *page_entry(unsigned long *sent, unsigned long iova)
+{
+       return (unsigned long *)__va(lv2table_base(sent)) + lv2ent_offset(iova);
+}
+
+enum exynos_sysmmu_inttype {
+       SYSMMU_PAGEFAULT,
+       SYSMMU_AR_MULTIHIT,
+       SYSMMU_AW_MULTIHIT,
+       SYSMMU_BUSERROR,
+       SYSMMU_AR_SECURITY,
+       SYSMMU_AR_ACCESS,
+       SYSMMU_AW_SECURITY,
+       SYSMMU_AW_PROTECTION, /* 7 */
+       SYSMMU_FAULT_UNKNOWN,
+       SYSMMU_FAULTS_NUM
+};
+
+typedef int (*sysmmu_fault_handler_t)(enum exynos_sysmmu_inttype itype,
+                       unsigned long pgtable_base, unsigned long fault_addr);
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+       REG_PAGE_FAULT_ADDR,
+       REG_AR_FAULT_ADDR,
+       REG_AW_FAULT_ADDR,
+       REG_DEFAULT_SLAVE_ADDR,
+       REG_AR_FAULT_ADDR,
+       REG_AR_FAULT_ADDR,
+       REG_AW_FAULT_ADDR,
+       REG_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+       "PAGE FAULT",
+       "AR MULTI-HIT FAULT",
+       "AW MULTI-HIT FAULT",
+       "BUS ERROR",
+       "AR SECURITY PROTECTION FAULT",
+       "AR ACCESS PROTECTION FAULT",
+       "AW SECURITY PROTECTION FAULT",
+       "AW ACCESS PROTECTION FAULT",
+       "UNKNOWN FAULT"
+};
+
+struct exynos_iommu_domain {
+       struct list_head clients; /* list of sysmmu_drvdata.node */
+       unsigned long *pgtable; /* lv1 page table, 16KB */
+       short *lv2entcnt; /* free lv2 entry counter for each section */
+       spinlock_t lock; /* lock for this structure */
+       spinlock_t pgtablelock; /* lock for modifying page table @ pgtable */
+};
+
+struct sysmmu_drvdata {
+       struct list_head node; /* entry of exynos_iommu_domain.clients */
+       struct device *sysmmu;  /* System MMU's device descriptor */
+       struct device *dev;     /* Owner of system MMU */
+       char *dbgname;
+       int nsfrs;
+       void __iomem **sfrbases;
+       struct clk *clk[2];
+       int activations;
+       rwlock_t lock;
+       struct iommu_domain *domain;
+       sysmmu_fault_handler_t fault_handler;
+       unsigned long pgtable;
+};
+
+static bool set_sysmmu_active(struct sysmmu_drvdata *data)
+{
+       /* return true if the System MMU was not active previously
+          and it needs to be initialized */
+       return ++data->activations == 1;
+}
+
+static bool set_sysmmu_inactive(struct sysmmu_drvdata *data)
+{
+       /* return true if the System MMU is needed to be disabled */
+       BUG_ON(data->activations < 1);
+       return --data->activations == 0;
+}
+
+static bool is_sysmmu_active(struct sysmmu_drvdata *data)
+{
+       return data->activations > 0;
+}
+
+static void sysmmu_block(void __iomem *sfrbase)
+{
+       __raw_writel(CTRL_BLOCK, sfrbase + REG_MMU_CTRL);
+}
+
+static void sysmmu_unblock(void __iomem *sfrbase)
+{
+       __raw_writel(CTRL_ENABLE, sfrbase + REG_MMU_CTRL);
+}
+
+static void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
+{
+       __raw_writel(0x1, sfrbase + REG_MMU_FLUSH);
+}
+
+static void __sysmmu_tlb_invalidate_entry(void __iomem *sfrbase,
+                                               unsigned long iova)
+{
+       __raw_writel((iova & SPAGE_MASK) | 1, sfrbase + REG_MMU_FLUSH_ENTRY);
+}
+
+static void __sysmmu_set_ptbase(void __iomem *sfrbase,
+                                      unsigned long pgd)
+{
+       __raw_writel(0x1, sfrbase + REG_MMU_CFG); /* 16KB LV1, LRU */
+       __raw_writel(pgd, sfrbase + REG_PT_BASE_ADDR);
+
+       __sysmmu_tlb_invalidate(sfrbase);
+}
+
+static void __sysmmu_set_prefbuf(void __iomem *sfrbase, unsigned long base,
+                                               unsigned long size, int idx)
+{
+       __raw_writel(base, sfrbase + REG_PB0_SADDR + idx * 8);
+       __raw_writel(size - 1 + base,  sfrbase + REG_PB0_EADDR + idx * 8);
+}
+
+void exynos_sysmmu_set_prefbuf(struct device *dev,
+                               unsigned long base0, unsigned long size0,
+                               unsigned long base1, unsigned long size1)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+       unsigned long flags;
+       int i;
+
+       BUG_ON((base0 + size0) <= base0);
+       BUG_ON((size1 > 0) && ((base1 + size1) <= base1));
+
+       read_lock_irqsave(&data->lock, flags);
+       if (!is_sysmmu_active(data))
+               goto finish;
+
+       for (i = 0; i < data->nsfrs; i++) {
+               if ((readl(data->sfrbases[i] + REG_MMU_VERSION) >> 28) == 3) {
+                       sysmmu_block(data->sfrbases[i]);
+
+                       if (size1 == 0) {
+                               if (size0 <= SZ_128K) {
+                                       base1 = base0;
+                                       size1 = size0;
+                               } else {
+                                       size1 = size0 -
+                                               ALIGN(size0 / 2, SZ_64K);
+                                       size0 = size0 - size1;
+                                       base1 = base0 + size0;
+                               }
+                       }
+
+                       __sysmmu_set_prefbuf(
+                                       data->sfrbases[i], base0, size0, 0);
+                       __sysmmu_set_prefbuf(
+                                       data->sfrbases[i], base1, size1, 1);
+
+                       sysmmu_unblock(data->sfrbases[i]);
+               }
+       }
+finish:
+       read_unlock_irqrestore(&data->lock, flags);
+}
+
+static void __set_fault_handler(struct sysmmu_drvdata *data,
+                                       sysmmu_fault_handler_t handler)
+{
+       unsigned long flags;
+
+       write_lock_irqsave(&data->lock, flags);
+       data->fault_handler = handler;
+       write_unlock_irqrestore(&data->lock, flags);
+}
+
+void exynos_sysmmu_set_fault_handler(struct device *dev,
+                                       sysmmu_fault_handler_t handler)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+
+       __set_fault_handler(data, handler);
+}
+
+static int default_fault_handler(enum exynos_sysmmu_inttype itype,
+                    unsigned long pgtable_base, unsigned long fault_addr)
+{
+       unsigned long *ent;
+
+       if ((itype >= SYSMMU_FAULTS_NUM) || (itype < SYSMMU_PAGEFAULT))
+               itype = SYSMMU_FAULT_UNKNOWN;
+
+       pr_err("%s occured at 0x%lx(Page table base: 0x%lx)\n",
+                       sysmmu_fault_name[itype], fault_addr, pgtable_base);
+
+       ent = section_entry(__va(pgtable_base), fault_addr);
+       pr_err("\tLv1 entry: 0x%lx\n", *ent);
+
+       if (lv1ent_page(ent)) {
+               ent = page_entry(ent, fault_addr);
+               pr_err("\t Lv2 entry: 0x%lx\n", *ent);
+       }
+
+       pr_err("Generating Kernel OOPS... because it is unrecoverable.\n");
+
+       BUG();
+
+       return 0;
+}
+
+static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
+{
+       /* SYSMMU is in blocked when interrupt occurred. */
+       struct sysmmu_drvdata *data = dev_id;
+       struct resource *irqres;
+       struct platform_device *pdev;
+       enum exynos_sysmmu_inttype itype;
+       unsigned long addr = -1;
+
+       int i, ret = -ENOSYS;
+
+       read_lock(&data->lock);
+
+       WARN_ON(!is_sysmmu_active(data));
+
+       pdev = to_platform_device(data->sysmmu);
+       for (i = 0; i < (pdev->num_resources / 2); i++) {
+               irqres = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+               if (irqres && ((int)irqres->start == irq))
+                       break;
+       }
+
+       if (i == pdev->num_resources) {
+               itype = SYSMMU_FAULT_UNKNOWN;
+       } else {
+               itype = (enum exynos_sysmmu_inttype)
+                       __ffs(__raw_readl(data->sfrbases[i] + REG_INT_STATUS));
+               if (WARN_ON(!((itype >= 0) && (itype < SYSMMU_FAULT_UNKNOWN))))
+                       itype = SYSMMU_FAULT_UNKNOWN;
+               else
+                       addr = __raw_readl(
+                               data->sfrbases[i] + fault_reg_offset[itype]);
+       }
+
+       if (data->domain)
+               ret = report_iommu_fault(data->domain, data->dev,
+                               addr, itype);
+
+       if ((ret == -ENOSYS) && data->fault_handler) {
+               unsigned long base = data->pgtable;
+               if (itype != SYSMMU_FAULT_UNKNOWN)
+                       base = __raw_readl(
+                                       data->sfrbases[i] + REG_PT_BASE_ADDR);
+               ret = data->fault_handler(itype, base, addr);
+       }
+
+       if (!ret && (itype != SYSMMU_FAULT_UNKNOWN))
+               __raw_writel(1 << itype, data->sfrbases[i] + REG_INT_CLEAR);
+       else
+               dev_dbg(data->sysmmu, "(%s) %s is not handled.\n",
+                               data->dbgname, sysmmu_fault_name[itype]);
+
+       if (itype != SYSMMU_FAULT_UNKNOWN)
+               sysmmu_unblock(data->sfrbases[i]);
+
+       read_unlock(&data->lock);
+
+       return IRQ_HANDLED;
+}
+
+static bool __exynos_sysmmu_disable(struct sysmmu_drvdata *data)
+{
+       unsigned long flags;
+       bool disabled = false;
+       int i;
+
+       write_lock_irqsave(&data->lock, flags);
+
+       if (!set_sysmmu_inactive(data))
+               goto finish;
+
+       for (i = 0; i < data->nsfrs; i++)
+               __raw_writel(CTRL_DISABLE, data->sfrbases[i] + REG_MMU_CTRL);
+
+       if (data->clk[1])
+               clk_disable(data->clk[1]);
+       if (data->clk[0])
+               clk_disable(data->clk[0]);
+
+       disabled = true;
+       data->pgtable = 0;
+       data->domain = NULL;
+finish:
+       write_unlock_irqrestore(&data->lock, flags);
+
+       if (disabled)
+               dev_dbg(data->sysmmu, "(%s) Disabled\n", data->dbgname);
+       else
+               dev_dbg(data->sysmmu, "(%s) %d times left to be disabled\n",
+                                       data->dbgname, data->activations);
+
+       return disabled;
+}
+
+/* __exynos_sysmmu_enable: Enables System MMU
+ *
+ * returns -error if an error occurred and System MMU is not enabled,
+ * 0 if the System MMU has been just enabled and 1 if System MMU was already
+ * enabled before.
+ */
+static int __exynos_sysmmu_enable(struct sysmmu_drvdata *data,
+                       unsigned long pgtable, struct iommu_domain *domain)
+{
+       int i, ret;
+       unsigned long flags;
+
+       write_lock_irqsave(&data->lock, flags);
+
+       if (!set_sysmmu_active(data)) {
+               if (WARN_ON(pgtable != data->pgtable)) {
+                       ret = -EBUSY;
+                       set_sysmmu_inactive(data);
+               } else {
+                       ret = 1;
+               }
+
+               dev_dbg(data->sysmmu, "(%s) Already enabled\n", data->dbgname);
+               goto finish;
+       }
+
+       ret = 0;
+
+       if (data->clk[0])
+               clk_enable(data->clk[0]);
+       if (data->clk[1])
+               clk_enable(data->clk[1]);
+
+       data->pgtable = pgtable;
+
+       for (i = 0; i < data->nsfrs; i++) {
+               __sysmmu_set_ptbase(data->sfrbases[i], pgtable);
+
+               if ((readl(data->sfrbases[i] + REG_MMU_VERSION) >> 28) == 3) {
+                       /* System MMU version is 3.x */
+                       __raw_writel((1 << 12) | (2 << 28),
+                                       data->sfrbases[i] + REG_MMU_CFG);
+                       __sysmmu_set_prefbuf(data->sfrbases[i], 0, -1, 0);
+                       __sysmmu_set_prefbuf(data->sfrbases[i], 0, -1, 1);
+               }
+
+               __raw_writel(CTRL_ENABLE, data->sfrbases[i] + REG_MMU_CTRL);
+       }
+
+       data->domain = domain;
+
+       dev_dbg(data->sysmmu, "(%s) Enabled\n", data->dbgname);
+finish:
+       write_unlock_irqrestore(&data->lock, flags);
+
+       if ((ret < 0) && (ret != -EBUSY)) {
+               __exynos_sysmmu_disable(data);
+               dev_dbg(data->sysmmu, "(%s) Failed to enable\n", data->dbgname);
+       }
+
+       return ret;
+}
+
+int exynos_sysmmu_enable(struct device *dev, unsigned long pgtable)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+       int ret;
+
+       BUG_ON(!memblock_is_memory(pgtable));
+
+       ret = pm_runtime_get_sync(data->sysmmu);
+       if (ret < 0)
+               return ret;
+
+       ret = __exynos_sysmmu_enable(data, pgtable, NULL);
+       if (ret < 0)
+               pm_runtime_put(data->sysmmu);
+       else
+               data->dev = dev;
+
+       return ret;
+}
+
+bool exynos_sysmmu_disable(struct device *dev)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+       bool disabled;
+
+       disabled = __exynos_sysmmu_disable(data);
+       pm_runtime_put(data->sysmmu);
+
+       return disabled;
+}
+
+static void sysmmu_tlb_invalidate_entry(struct device *dev, unsigned long iova)
+{
+       unsigned long flags;
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+
+       read_lock_irqsave(&data->lock, flags);
+
+       if (is_sysmmu_active(data)) {
+               int i;
+               for (i = 0; i < data->nsfrs; i++) {
+                       sysmmu_block(data->sfrbases[i]);
+                       __sysmmu_tlb_invalidate_entry(data->sfrbases[i], iova);
+                       sysmmu_unblock(data->sfrbases[i]);
+               }
+       } else {
+               dev_dbg(data->sysmmu,
+                       "(%s) Disabled. Skipping invalidating TLB.\n",
+                       data->dbgname);
+       }
+
+       read_unlock_irqrestore(&data->lock, flags);
+}
+
+void exynos_sysmmu_tlb_invalidate(struct device *dev)
+{
+       unsigned long flags;
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+
+       read_lock_irqsave(&data->lock, flags);
+
+       if (is_sysmmu_active(data)) {
+               int i;
+               for (i = 0; i < data->nsfrs; i++) {
+                       sysmmu_block(data->sfrbases[i]);
+                       __sysmmu_tlb_invalidate(data->sfrbases[i]);
+                       sysmmu_unblock(data->sfrbases[i]);
+               }
+       } else {
+               dev_dbg(data->sysmmu,
+                       "(%s) Disabled. Skipping invalidating TLB.\n",
+                       data->dbgname);
+       }
+
+       read_unlock_irqrestore(&data->lock, flags);
+}
+
+static int exynos_sysmmu_probe(struct platform_device *pdev)
+{
+       int i, ret;
+       struct device *dev;
+       struct sysmmu_drvdata *data;
+
+       dev = &pdev->dev;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data) {
+               dev_dbg(dev, "Not enough memory\n");
+               ret = -ENOMEM;
+               goto err_alloc;
+       }
+
+       ret = dev_set_drvdata(dev, data);
+       if (ret) {
+               dev_dbg(dev, "Unabled to initialize driver data\n");
+               goto err_init;
+       }
+
+       data->nsfrs = pdev->num_resources / 2;
+       data->sfrbases = kmalloc(sizeof(*data->sfrbases) * data->nsfrs,
+                                                               GFP_KERNEL);
+       if (data->sfrbases == NULL) {
+               dev_dbg(dev, "Not enough memory\n");
+               ret = -ENOMEM;
+               goto err_init;
+       }
+
+       for (i = 0; i < data->nsfrs; i++) {
+               struct resource *res;
+               res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+               if (!res) {
+                       dev_dbg(dev, "Unable to find IOMEM region\n");
+                       ret = -ENOENT;
+                       goto err_res;
+               }
+
+               data->sfrbases[i] = ioremap(res->start, resource_size(res));
+               if (!data->sfrbases[i]) {
+                       dev_dbg(dev, "Unable to map IOMEM @ PA:%#x\n",
+                                                       res->start);
+                       ret = -ENOENT;
+                       goto err_res;
+               }
+       }
+
+       for (i = 0; i < data->nsfrs; i++) {
+               ret = platform_get_irq(pdev, i);
+               if (ret <= 0) {
+                       dev_dbg(dev, "Unable to find IRQ resource\n");
+                       goto err_irq;
+               }
+
+               ret = request_irq(ret, exynos_sysmmu_irq, 0,
+                                       dev_name(dev), data);
+               if (ret) {
+                       dev_dbg(dev, "Unabled to register interrupt handler\n");
+                       goto err_irq;
+               }
+       }
+
+       if (dev_get_platdata(dev)) {
+               char *deli, *beg;
+               struct sysmmu_platform_data *platdata = dev_get_platdata(dev);
+
+               beg = platdata->clockname;
+
+               for (deli = beg; (*deli != '\0') && (*deli != ','); deli++)
+                       /* NOTHING */;
+
+               if (*deli == '\0')
+                       deli = NULL;
+               else
+                       *deli = '\0';
+
+               data->clk[0] = clk_get(dev, beg);
+               if (IS_ERR(data->clk[0])) {
+                       data->clk[0] = NULL;
+                       dev_dbg(dev, "No clock descriptor registered\n");
+               }
+
+               if (data->clk[0] && deli) {
+                       *deli = ',';
+                       data->clk[1] = clk_get(dev, deli + 1);
+                       if (IS_ERR(data->clk[1]))
+                               data->clk[1] = NULL;
+               }
+
+               data->dbgname = platdata->dbgname;
+       }
+
+       data->sysmmu = dev;
+       rwlock_init(&data->lock);
+       INIT_LIST_HEAD(&data->node);
+
+       __set_fault_handler(data, &default_fault_handler);
+
+       pm_runtime_enable(dev);
+
+       dev_dbg(dev, "(%s) Initialized\n", data->dbgname);
+       return 0;
+err_irq:
+       while (i-- > 0) {
+               int irq;
+
+               irq = platform_get_irq(pdev, i);
+               free_irq(irq, data);
+       }
+err_res:
+       while (data->nsfrs-- > 0)
+               iounmap(data->sfrbases[data->nsfrs]);
+       kfree(data->sfrbases);
+err_init:
+       kfree(data);
+err_alloc:
+       dev_err(dev, "Failed to initialize\n");
+       return ret;
+}
+
+static int exynos_pm_resume(struct device *dev)
+{
+       struct sysmmu_drvdata *data;
+
+       data = dev_get_drvdata(dev);
+
+       if (is_sysmmu_active(data))
+               __exynos_sysmmu_enable(data, data->pgtable, NULL);
+
+       return 0;
+}
+
+const struct dev_pm_ops exynos_pm_ops = {
+       .resume = &exynos_pm_resume,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id sysmmu_of_match[] = {
+       { .compatible = "samsung,s5p-sysmmu" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, sysmmu_of_match);
+#else
+#define sysmmu_of_match NULL
+#endif
+
+static struct platform_driver exynos_sysmmu_driver = {
+       .probe          = exynos_sysmmu_probe,
+       .driver         = {
+               .owner          = THIS_MODULE,
+               .name           = "s5p-sysmmu",
+               .pm             = &exynos_pm_ops,
+               .of_match_table = sysmmu_of_match,
+       }
+};
+
+static inline void pgtable_flush(void *vastart, void *vaend)
+{
+       dmac_flush_range(vastart, vaend);
+       outer_flush_range(virt_to_phys(vastart),
+                               virt_to_phys(vaend));
+}
+
+static int exynos_iommu_domain_init(struct iommu_domain *domain)
+{
+       struct exynos_iommu_domain *priv;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->pgtable = (unsigned long *)__get_free_pages(
+                                               GFP_KERNEL | __GFP_ZERO, 2);
+       if (!priv->pgtable)
+               goto err_pgtable;
+
+       priv->lv2entcnt = (short *)__get_free_pages(
+                                               GFP_KERNEL | __GFP_ZERO, 1);
+       if (!priv->lv2entcnt)
+               goto err_counter;
+
+       pgtable_flush(priv->pgtable, priv->pgtable + NUM_LV1ENTRIES);
+
+       spin_lock_init(&priv->lock);
+       spin_lock_init(&priv->pgtablelock);
+       INIT_LIST_HEAD(&priv->clients);
+
+       domain->priv = priv;
+       return 0;
+
+err_counter:
+       free_pages((unsigned long)priv->pgtable, 2);
+err_pgtable:
+       kfree(priv);
+       return -ENOMEM;
+}
+
+static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
+{
+       struct exynos_iommu_domain *priv = domain->priv;
+       struct sysmmu_drvdata *data;
+       unsigned long flags;
+       int i;
+
+       WARN_ON(!list_empty(&priv->clients));
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       list_for_each_entry(data, &priv->clients, node) {
+               while (!exynos_sysmmu_disable(data->dev))
+                       ; /* until System MMU is actually disabled */
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       for (i = 0; i < NUM_LV1ENTRIES; i++)
+               if (lv1ent_page(priv->pgtable + i))
+                       kfree(__va(lv2table_base(priv->pgtable + i)));
+
+       free_pages((unsigned long)priv->pgtable, 2);
+       free_pages((unsigned long)priv->lv2entcnt, 1);
+       kfree(domain->priv);
+       domain->priv = NULL;
+}
+
+static int exynos_iommu_attach_device(struct iommu_domain *domain,
+                                  struct device *dev)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+       struct exynos_iommu_domain *priv = domain->priv;
+       unsigned long flags;
+       int ret;
+
+       /* If there is no SYSMMU, this could be a virtual device with a common
+        * IOMMU mapping shared with another device e.g. DRM with DRM-FIMD
+        */
+       if (data==NULL) {
+               dev_err(dev, "No SYSMMU found\n");
+               return 0;
+       }
+
+       ret = pm_runtime_get_sync(data->sysmmu);
+       if (ret < 0)
+               return ret;
+
+       ret = 0;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       ret = __exynos_sysmmu_enable(data, __pa(priv->pgtable), domain);
+
+       if (ret == 0) {
+               /* 'data->node' must not appear in priv->clients */
+               BUG_ON(!list_empty(&data->node));
+               data->dev = dev;
+               list_add_tail(&data->node, &priv->clients);
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       if (ret < 0) {
+               dev_err(dev, "%s: Failed to attach IOMMU with pgtable %#lx\n",
+                               __func__, __pa(priv->pgtable));
+               pm_runtime_put(data->sysmmu);
+       } else if (ret > 0) {
+               dev_dbg(dev, "%s: IOMMU with pgtable 0x%lx already attached\n",
+                                       __func__, __pa(priv->pgtable));
+       } else {
+               dev_dbg(dev, "%s: Attached new IOMMU with pgtable 0x%lx\n",
+                                       __func__, __pa(priv->pgtable));
+       }
+
+       return ret;
+}
+
+static void exynos_iommu_detach_device(struct iommu_domain *domain,
+                                   struct device *dev)
+{
+       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
+       struct exynos_iommu_domain *priv = domain->priv;
+       struct list_head *pos;
+       unsigned long flags;
+       bool found = false;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       list_for_each(pos, &priv->clients) {
+               if (list_entry(pos, struct sysmmu_drvdata, node) == data) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found)
+               goto finish;
+
+       if (__exynos_sysmmu_disable(data)) {
+               dev_dbg(dev, "%s: Detached IOMMU with pgtable %#lx\n",
+                                       __func__, __pa(priv->pgtable));
+               list_del(&data->node);
+               INIT_LIST_HEAD(&data->node);
+
+       } else {
+               dev_dbg(dev, "%s: Detaching IOMMU with pgtable %#lx delayed",
+                                       __func__, __pa(priv->pgtable));
+       }
+
+finish:
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       if (found)
+               pm_runtime_put(data->sysmmu);
+}
+
+static unsigned long *alloc_lv2entry(unsigned long *sent, unsigned long iova,
+                                       short *pgcounter)
+{
+       if (lv1ent_fault(sent)) {
+               unsigned long *pent;
+
+               pent = kzalloc(LV2TABLE_SIZE, GFP_ATOMIC);
+               BUG_ON((unsigned long)pent & (LV2TABLE_SIZE - 1));
+               if (!pent)
+                       return NULL;
+
+               *sent = mk_lv1ent_page(__pa(pent));
+               *pgcounter = NUM_LV2ENTRIES;
+               pgtable_flush(pent, pent + NUM_LV2ENTRIES);
+               pgtable_flush(sent, sent + 1);
+       }
+
+       return page_entry(sent, iova);
+}
+
+static int lv1set_section(unsigned long *sent, phys_addr_t paddr, short *pgcnt)
+{
+       if (lv1ent_section(sent))
+               return -EADDRINUSE;
+
+       if (lv1ent_page(sent)) {
+               if (*pgcnt != NUM_LV2ENTRIES)
+                       return -EADDRINUSE;
+
+               kfree(page_entry(sent, 0));
+
+               *pgcnt = 0;
+       }
+
+       *sent = mk_lv1ent_sect(paddr);
+
+       pgtable_flush(sent, sent + 1);
+
+       return 0;
+}
+
+static int lv2set_page(unsigned long *pent, phys_addr_t paddr, size_t size,
+                                                               short *pgcnt)
+{
+       if (size == SPAGE_SIZE) {
+               if (!lv2ent_fault(pent))
+                       return -EADDRINUSE;
+
+               *pent = mk_lv2ent_spage(paddr);
+               pgtable_flush(pent, pent + 1);
+               *pgcnt -= 1;
+       } else { /* size == LPAGE_SIZE */
+               int i;
+               for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) {
+                       if (!lv2ent_fault(pent)) {
+                               memset(pent, 0, sizeof(*pent) * i);
+                               return -EADDRINUSE;
+                       }
+
+                       *pent = mk_lv2ent_lpage(paddr);
+               }
+               pgtable_flush(pent - SPAGES_PER_LPAGE, pent);
+               *pgcnt -= SPAGES_PER_LPAGE;
+       }
+
+       return 0;
+}
+
+static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
+                        phys_addr_t paddr, size_t size, int prot)
+{
+       struct exynos_iommu_domain *priv = domain->priv;
+       unsigned long *entry;
+       unsigned long flags;
+       int ret = -ENOMEM;
+
+       BUG_ON(priv->pgtable == NULL);
+
+       spin_lock_irqsave(&priv->pgtablelock, flags);
+
+       entry = section_entry(priv->pgtable, iova);
+
+       if (size == SECT_SIZE) {
+               ret = lv1set_section(entry, paddr,
+                                       &priv->lv2entcnt[lv1ent_offset(iova)]);
+       } else {
+               unsigned long *pent;
+
+               pent = alloc_lv2entry(entry, iova,
+                                       &priv->lv2entcnt[lv1ent_offset(iova)]);
+
+               if (!pent)
+                       ret = -ENOMEM;
+               else
+                       ret = lv2set_page(pent, paddr, size,
+                                       &priv->lv2entcnt[lv1ent_offset(iova)]);
+       }
+
+       if (ret) {
+               pr_debug("%s: Failed to map iova 0x%lx/0x%x bytes\n",
+                                                       __func__, iova, size);
+       }
+
+       spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+       return ret;
+}
+
+static size_t exynos_iommu_unmap(struct iommu_domain *domain,
+                                              unsigned long iova, size_t size)
+{
+       struct exynos_iommu_domain *priv = domain->priv;
+       struct sysmmu_drvdata *data;
+       unsigned long flags;
+       unsigned long *ent;
+
+       BUG_ON(priv->pgtable == NULL);
+
+       spin_lock_irqsave(&priv->pgtablelock, flags);
+
+       ent = section_entry(priv->pgtable, iova);
+
+       if (lv1ent_section(ent)) {
+               BUG_ON(size < SECT_SIZE);
+
+               *ent = 0;
+               pgtable_flush(ent, ent + 1);
+               size = SECT_SIZE;
+               goto done;
+       }
+
+       if (unlikely(lv1ent_fault(ent))) {
+               if (size > SECT_SIZE)
+                       size = SECT_SIZE;
+               goto done;
+       }
+
+       /* lv1ent_page(sent) == true here */
+
+       ent = page_entry(ent, iova);
+
+       if (unlikely(lv2ent_fault(ent))) {
+               size = SPAGE_SIZE;
+               goto done;
+       }
+
+       if (lv2ent_small(ent)) {
+               *ent = 0;
+               size = SPAGE_SIZE;
+               priv->lv2entcnt[lv1ent_offset(iova)] += 1;
+               goto done;
+       }
+
+       /* lv1ent_large(ent) == true here */
+       BUG_ON(size < LPAGE_SIZE);
+
+       memset(ent, 0, sizeof(*ent) * SPAGES_PER_LPAGE);
+
+       size = LPAGE_SIZE;
+       priv->lv2entcnt[lv1ent_offset(iova)] += SPAGES_PER_LPAGE;
+done:
+       spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       list_for_each_entry(data, &priv->clients, node)
+               sysmmu_tlb_invalidate_entry(data->dev, iova);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+
+       return size;
+}
+
+static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
+                                         unsigned long iova)
+{
+       struct exynos_iommu_domain *priv = domain->priv;
+       unsigned long *entry;
+       unsigned long flags;
+       phys_addr_t phys = 0;
+
+       spin_lock_irqsave(&priv->pgtablelock, flags);
+
+       entry = section_entry(priv->pgtable, iova);
+
+       if (lv1ent_section(entry)) {
+               phys = section_phys(entry) + section_offs(iova);
+       } else if (lv1ent_page(entry)) {
+               entry = page_entry(entry, iova);
+
+               if (lv2ent_large(entry))
+                       phys = lpage_phys(entry) + lpage_offs(iova);
+               else if (lv2ent_small(entry))
+                       phys = spage_phys(entry) + spage_offs(iova);
+       }
+
+       spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+       return phys;
+}
+
+static struct iommu_ops exynos_iommu_ops = {
+       .domain_init = &exynos_iommu_domain_init,
+       .domain_destroy = &exynos_iommu_domain_destroy,
+       .attach_dev = &exynos_iommu_attach_device,
+       .detach_dev = &exynos_iommu_detach_device,
+       .map = &exynos_iommu_map,
+       .unmap = &exynos_iommu_unmap,
+       .iova_to_phys = &exynos_iommu_iova_to_phys,
+       .pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE,
+};
+
+static int __init exynos_iommu_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&exynos_sysmmu_driver);
+
+       if (ret == 0)
+               bus_set_iommu(&platform_bus_type, &exynos_iommu_ops);
+
+       return ret;
+}
+arch_initcall(exynos_iommu_init);
index ce1e7ba..b238d88 100644 (file)
@@ -625,6 +625,7 @@ config VIDEO_VIVI
        depends on FRAMEBUFFER_CONSOLE || STI_CONSOLE
        select FONT_8x16
        select VIDEOBUF2_VMALLOC
+       select DMA_SHARED_BUFFER
        default n
        ---help---
          Enables a virtual video driver. This device shows a color bar
index 7e9b2c6..7995ece 100644 (file)
@@ -1020,6 +1020,14 @@ static int fimc_cap_qbuf(struct file *file, void *priv,
        return vb2_qbuf(&fimc->vid_cap.vbq, buf);
 }
 
+static int fimc_cap_expbuf(struct file *file, void *priv,
+                         struct v4l2_exportbuffer *eb)
+{
+       struct fimc_dev *fimc = video_drvdata(file);
+
+       return vb2_expbuf(&fimc->vid_cap.vbq, eb);
+}
+
 static int fimc_cap_dqbuf(struct file *file, void *priv,
                           struct v4l2_buffer *buf)
 {
@@ -1155,6 +1163,7 @@ static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = {
 
        .vidioc_qbuf                    = fimc_cap_qbuf,
        .vidioc_dqbuf                   = fimc_cap_dqbuf,
+       .vidioc_expbuf                  = fimc_cap_expbuf,
 
        .vidioc_prepare_buf             = fimc_cap_prepare_buf,
        .vidioc_create_bufs             = fimc_cap_create_bufs,
index c25ec02..8344ce5 100644 (file)
@@ -564,6 +564,23 @@ static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
        return -EINVAL;
 }
 
+/* Export DMA buffer */
+static int vidioc_expbuf(struct file *file, void *priv,
+       struct v4l2_exportbuffer *eb)
+{
+       struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+       int ret;
+
+       if (eb->mem_offset < DST_QUEUE_OFF_BASE)
+               return vb2_expbuf(&ctx->vq_src, eb);
+
+       eb->mem_offset -= DST_QUEUE_OFF_BASE;
+       ret = vb2_expbuf(&ctx->vq_dst, eb);
+       eb->mem_offset += DST_QUEUE_OFF_BASE;
+
+       return ret;
+}
+
 /* Stream on */
 static int vidioc_streamon(struct file *file, void *priv,
                           enum v4l2_buf_type type)
@@ -739,6 +756,7 @@ static const struct v4l2_ioctl_ops s5p_mfc_dec_ioctl_ops = {
        .vidioc_querybuf = vidioc_querybuf,
        .vidioc_qbuf = vidioc_qbuf,
        .vidioc_dqbuf = vidioc_dqbuf,
+       .vidioc_expbuf = vidioc_expbuf,
        .vidioc_streamon = vidioc_streamon,
        .vidioc_streamoff = vidioc_streamoff,
        .vidioc_g_crop = vidioc_g_crop,
index dff9dc7..a8330bf 100644 (file)
@@ -1141,6 +1141,23 @@ static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
        return -EINVAL;
 }
 
+/* Export DMA buffer */
+static int vidioc_expbuf(struct file *file, void *priv,
+       struct v4l2_exportbuffer *eb)
+{
+       struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+       int ret;
+
+       if (eb->mem_offset < DST_QUEUE_OFF_BASE)
+               return vb2_expbuf(&ctx->vq_src, eb);
+
+       eb->mem_offset -= DST_QUEUE_OFF_BASE;
+       ret = vb2_expbuf(&ctx->vq_dst, eb);
+       eb->mem_offset += DST_QUEUE_OFF_BASE;
+
+       return ret;
+}
+
 /* Stream on */
 static int vidioc_streamon(struct file *file, void *priv,
                           enum v4l2_buf_type type)
@@ -1484,6 +1501,7 @@ static const struct v4l2_ioctl_ops s5p_mfc_enc_ioctl_ops = {
        .vidioc_querybuf = vidioc_querybuf,
        .vidioc_qbuf = vidioc_qbuf,
        .vidioc_dqbuf = vidioc_dqbuf,
+       .vidioc_expbuf = vidioc_expbuf,
        .vidioc_streamon = vidioc_streamon,
        .vidioc_streamoff = vidioc_streamoff,
        .vidioc_s_parm = vidioc_s_parm,
index f248b28..2e80126 100644 (file)
@@ -10,6 +10,7 @@ config VIDEO_SAMSUNG_S5P_TV
        bool "Samsung TV driver for S5P platform (experimental)"
        depends on PLAT_S5P && PM_RUNTIME
        depends on EXPERIMENTAL
+       select DMA_SHARED_BUFFER
        default n
        ---help---
          Say Y here to enable selecting the TV output devices for
index f7ca5cc..f08edbf 100644 (file)
@@ -697,6 +697,15 @@ static int mxr_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
        return vb2_dqbuf(&layer->vb_queue, p, file->f_flags & O_NONBLOCK);
 }
 
+static int mxr_expbuf(struct file *file, void *priv,
+       struct v4l2_exportbuffer *eb)
+{
+       struct mxr_layer *layer = video_drvdata(file);
+
+       mxr_dbg(layer->mdev, "%s:%d\n", __func__, __LINE__);
+       return vb2_expbuf(&layer->vb_queue, eb);
+}
+
 static int mxr_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
 {
        struct mxr_layer *layer = video_drvdata(file);
@@ -724,6 +733,7 @@ static const struct v4l2_ioctl_ops mxr_ioctl_ops = {
        .vidioc_querybuf = mxr_querybuf,
        .vidioc_qbuf = mxr_qbuf,
        .vidioc_dqbuf = mxr_dqbuf,
+       .vidioc_expbuf = mxr_expbuf,
        /* Streaming control */
        .vidioc_streamon = mxr_streamon,
        .vidioc_streamoff = mxr_streamoff,
@@ -1074,7 +1084,7 @@ struct mxr_layer *mxr_base_layer_create(struct mxr_device *mdev,
 
        layer->vb_queue = (struct vb2_queue) {
                .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
-               .io_modes = VB2_MMAP | VB2_USERPTR,
+               .io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF,
                .drv_priv = layer,
                .buf_struct_size = sizeof(struct mxr_buffer),
                .ops = &mxr_video_qops,
index 2829d25..df4e038 100644 (file)
@@ -348,6 +348,9 @@ static int get_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32,
                up_pln = compat_ptr(p);
                if (put_user((unsigned long)up_pln, &up->m.userptr))
                        return -EFAULT;
+       } else if (memory == V4L2_MEMORY_DMABUF) {
+               if (copy_in_user(&up->m.fd, &up32->m.fd, sizeof(int)))
+                       return -EFAULT;
        } else {
                if (copy_in_user(&up->m.mem_offset, &up32->m.mem_offset,
                                        sizeof(__u32)))
@@ -371,6 +374,11 @@ static int put_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32,
                if (copy_in_user(&up32->m.mem_offset, &up->m.mem_offset,
                                        sizeof(__u32)))
                        return -EFAULT;
+       /* For DMABUF, driver might've set up the fd, so copy it back. */
+       if (memory == V4L2_MEMORY_DMABUF)
+               if (copy_in_user(&up32->m.fd, &up->m.fd,
+                                       sizeof(int)))
+                       return -EFAULT;
 
        return 0;
 }
@@ -454,6 +462,10 @@ static int get_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user
                        if (get_user(kp->m.offset, &up->m.offset))
                                return -EFAULT;
                        break;
+               case V4L2_MEMORY_DMABUF:
+                       if (get_user(kp->m.fd, &up->m.fd))
+                               return -EFAULT;
+                       break;
                }
        }
 
@@ -518,6 +530,10 @@ static int put_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user
                        if (put_user(kp->m.offset, &up->m.offset))
                                return -EFAULT;
                        break;
+               case V4L2_MEMORY_DMABUF:
+                       if (put_user(kp->m.fd, &up->m.fd))
+                               return -EFAULT;
+                       break;
                }
        }
 
@@ -954,6 +970,7 @@ long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
        case VIDIOC_S_FBUF32:
        case VIDIOC_OVERLAY32:
        case VIDIOC_QBUF32:
+       case VIDIOC_EXPBUF:
        case VIDIOC_DQBUF32:
        case VIDIOC_STREAMON32:
        case VIDIOC_STREAMOFF32:
index 70bec54..734cee0 100644 (file)
@@ -322,11 +322,19 @@ static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
        int ret = -ENODEV;
 
        if (vdev->fops->unlocked_ioctl) {
-               if (vdev->lock && mutex_lock_interruptible(vdev->lock))
-                       return -ERESTARTSYS;
+               bool locked = false;
+
+               if (vdev->lock) {
+                       /* always lock unless the cmd is marked as "don't use lock" */
+                       locked = !v4l2_is_known_ioctl(cmd) ||
+                                !test_bit(_IOC_NR(cmd), vdev->dont_use_lock);
+
+                       if (locked && mutex_lock_interruptible(vdev->lock))
+                               return -ERESTARTSYS;
+               }
                if (video_is_registered(vdev))
                        ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
-               if (vdev->lock)
+               if (locked)
                        mutex_unlock(vdev->lock);
        } else if (vdev->fops->ioctl) {
                /* This code path is a replacement for the BKL. It is a major
@@ -508,6 +516,176 @@ static int get_index(struct video_device *vdev)
        return find_first_zero_bit(used, VIDEO_NUM_DEVICES);
 }
 
+#define SET_VALID_IOCTL(ops, cmd, op)                  \
+       if (ops->op)                                    \
+               set_bit(_IOC_NR(cmd), valid_ioctls)
+
+/* This determines which ioctls are actually implemented in the driver.
+   It's a one-time thing which simplifies video_ioctl2 as it can just do
+   a bit test.
+
+   Note that drivers can override this by setting bits to 1 in
+   vdev->valid_ioctls. If an ioctl is marked as 1 when this function is
+   called, then that ioctl will actually be marked as unimplemented.
+
+   It does that by first setting up the local valid_ioctls bitmap, and
+   at the end do a:
+
+   vdev->valid_ioctls = valid_ioctls & ~(vdev->valid_ioctls)
+ */
+static void determine_valid_ioctls(struct video_device *vdev)
+{
+       DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
+       const struct v4l2_ioctl_ops *ops = vdev->ioctl_ops;
+
+       bitmap_zero(valid_ioctls, BASE_VIDIOC_PRIVATE);
+
+       SET_VALID_IOCTL(ops, VIDIOC_QUERYCAP, vidioc_querycap);
+       if (ops->vidioc_g_priority ||
+                       test_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags))
+               set_bit(_IOC_NR(VIDIOC_G_PRIORITY), valid_ioctls);
+       if (ops->vidioc_s_priority ||
+                       test_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags))
+               set_bit(_IOC_NR(VIDIOC_S_PRIORITY), valid_ioctls);
+       if (ops->vidioc_enum_fmt_vid_cap ||
+           ops->vidioc_enum_fmt_vid_out ||
+           ops->vidioc_enum_fmt_vid_cap_mplane ||
+           ops->vidioc_enum_fmt_vid_out_mplane ||
+           ops->vidioc_enum_fmt_vid_overlay ||
+           ops->vidioc_enum_fmt_type_private)
+               set_bit(_IOC_NR(VIDIOC_ENUM_FMT), valid_ioctls);
+       if (ops->vidioc_g_fmt_vid_cap ||
+           ops->vidioc_g_fmt_vid_out ||
+           ops->vidioc_g_fmt_vid_cap_mplane ||
+           ops->vidioc_g_fmt_vid_out_mplane ||
+           ops->vidioc_g_fmt_vid_overlay ||
+           ops->vidioc_g_fmt_vbi_cap ||
+           ops->vidioc_g_fmt_vid_out_overlay ||
+           ops->vidioc_g_fmt_vbi_out ||
+           ops->vidioc_g_fmt_sliced_vbi_cap ||
+           ops->vidioc_g_fmt_sliced_vbi_out ||
+           ops->vidioc_g_fmt_type_private)
+               set_bit(_IOC_NR(VIDIOC_G_FMT), valid_ioctls);
+       if (ops->vidioc_s_fmt_vid_cap ||
+           ops->vidioc_s_fmt_vid_out ||
+           ops->vidioc_s_fmt_vid_cap_mplane ||
+           ops->vidioc_s_fmt_vid_out_mplane ||
+           ops->vidioc_s_fmt_vid_overlay ||
+           ops->vidioc_s_fmt_vbi_cap ||
+           ops->vidioc_s_fmt_vid_out_overlay ||
+           ops->vidioc_s_fmt_vbi_out ||
+           ops->vidioc_s_fmt_sliced_vbi_cap ||
+           ops->vidioc_s_fmt_sliced_vbi_out ||
+           ops->vidioc_s_fmt_type_private)
+               set_bit(_IOC_NR(VIDIOC_S_FMT), valid_ioctls);
+       if (ops->vidioc_try_fmt_vid_cap ||
+           ops->vidioc_try_fmt_vid_out ||
+           ops->vidioc_try_fmt_vid_cap_mplane ||
+           ops->vidioc_try_fmt_vid_out_mplane ||
+           ops->vidioc_try_fmt_vid_overlay ||
+           ops->vidioc_try_fmt_vbi_cap ||
+           ops->vidioc_try_fmt_vid_out_overlay ||
+           ops->vidioc_try_fmt_vbi_out ||
+           ops->vidioc_try_fmt_sliced_vbi_cap ||
+           ops->vidioc_try_fmt_sliced_vbi_out ||
+           ops->vidioc_try_fmt_type_private)
+               set_bit(_IOC_NR(VIDIOC_TRY_FMT), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_REQBUFS, vidioc_reqbufs);
+       SET_VALID_IOCTL(ops, VIDIOC_QUERYBUF, vidioc_querybuf);
+       SET_VALID_IOCTL(ops, VIDIOC_QBUF, vidioc_qbuf);
+       SET_VALID_IOCTL(ops, VIDIOC_EXPBUF, vidioc_expbuf);
+       SET_VALID_IOCTL(ops, VIDIOC_DQBUF, vidioc_dqbuf);
+       SET_VALID_IOCTL(ops, VIDIOC_OVERLAY, vidioc_overlay);
+       SET_VALID_IOCTL(ops, VIDIOC_G_FBUF, vidioc_g_fbuf);
+       SET_VALID_IOCTL(ops, VIDIOC_S_FBUF, vidioc_s_fbuf);
+       SET_VALID_IOCTL(ops, VIDIOC_STREAMON, vidioc_streamon);
+       SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff);
+       if (vdev->tvnorms)
+               set_bit(_IOC_NR(VIDIOC_ENUMSTD), valid_ioctls);
+       if (ops->vidioc_g_std || vdev->current_norm)
+               set_bit(_IOC_NR(VIDIOC_G_STD), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_S_STD, vidioc_s_std);
+       SET_VALID_IOCTL(ops, VIDIOC_QUERYSTD, vidioc_querystd);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUMINPUT, vidioc_enum_input);
+       SET_VALID_IOCTL(ops, VIDIOC_G_INPUT, vidioc_g_input);
+       SET_VALID_IOCTL(ops, VIDIOC_S_INPUT, vidioc_s_input);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUMOUTPUT, vidioc_enum_output);
+       SET_VALID_IOCTL(ops, VIDIOC_G_OUTPUT, vidioc_g_output);
+       SET_VALID_IOCTL(ops, VIDIOC_S_OUTPUT, vidioc_s_output);
+       /* Note: the control handler can also be passed through the filehandle,
+          and that can't be tested here. If the bit for these control ioctls
+          is set, then the ioctl is valid. But if it is 0, then it can still
+          be valid if the filehandle passed the control handler. */
+       if (vdev->ctrl_handler || ops->vidioc_queryctrl)
+               set_bit(_IOC_NR(VIDIOC_QUERYCTRL), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_g_ctrl || ops->vidioc_g_ext_ctrls)
+               set_bit(_IOC_NR(VIDIOC_G_CTRL), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_s_ctrl || ops->vidioc_s_ext_ctrls)
+               set_bit(_IOC_NR(VIDIOC_S_CTRL), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_g_ext_ctrls)
+               set_bit(_IOC_NR(VIDIOC_G_EXT_CTRLS), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_s_ext_ctrls)
+               set_bit(_IOC_NR(VIDIOC_S_EXT_CTRLS), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_try_ext_ctrls)
+               set_bit(_IOC_NR(VIDIOC_TRY_EXT_CTRLS), valid_ioctls);
+       if (vdev->ctrl_handler || ops->vidioc_querymenu)
+               set_bit(_IOC_NR(VIDIOC_QUERYMENU), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUMAUDIO, vidioc_enumaudio);
+       SET_VALID_IOCTL(ops, VIDIOC_G_AUDIO, vidioc_g_audio);
+       SET_VALID_IOCTL(ops, VIDIOC_S_AUDIO, vidioc_s_audio);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUMAUDOUT, vidioc_enumaudout);
+       SET_VALID_IOCTL(ops, VIDIOC_G_AUDOUT, vidioc_g_audout);
+       SET_VALID_IOCTL(ops, VIDIOC_S_AUDOUT, vidioc_s_audout);
+       SET_VALID_IOCTL(ops, VIDIOC_G_MODULATOR, vidioc_g_modulator);
+       SET_VALID_IOCTL(ops, VIDIOC_S_MODULATOR, vidioc_s_modulator);
+       if (ops->vidioc_g_crop || ops->vidioc_g_selection)
+               set_bit(_IOC_NR(VIDIOC_G_CROP), valid_ioctls);
+       if (ops->vidioc_s_crop || ops->vidioc_s_selection)
+               set_bit(_IOC_NR(VIDIOC_S_CROP), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_G_SELECTION, vidioc_g_selection);
+       SET_VALID_IOCTL(ops, VIDIOC_S_SELECTION, vidioc_s_selection);
+       if (ops->vidioc_cropcap || ops->vidioc_g_selection)
+               set_bit(_IOC_NR(VIDIOC_CROPCAP), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp);
+       SET_VALID_IOCTL(ops, VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp);
+       SET_VALID_IOCTL(ops, VIDIOC_G_ENC_INDEX, vidioc_g_enc_index);
+       SET_VALID_IOCTL(ops, VIDIOC_ENCODER_CMD, vidioc_encoder_cmd);
+       SET_VALID_IOCTL(ops, VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd);
+       SET_VALID_IOCTL(ops, VIDIOC_DECODER_CMD, vidioc_decoder_cmd);
+       SET_VALID_IOCTL(ops, VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd);
+       if (ops->vidioc_g_parm || vdev->current_norm)
+               set_bit(_IOC_NR(VIDIOC_G_PARM), valid_ioctls);
+       SET_VALID_IOCTL(ops, VIDIOC_S_PARM, vidioc_s_parm);
+       SET_VALID_IOCTL(ops, VIDIOC_G_TUNER, vidioc_g_tuner);
+       SET_VALID_IOCTL(ops, VIDIOC_S_TUNER, vidioc_s_tuner);
+       SET_VALID_IOCTL(ops, VIDIOC_G_FREQUENCY, vidioc_g_frequency);
+       SET_VALID_IOCTL(ops, VIDIOC_S_FREQUENCY, vidioc_s_frequency);
+       SET_VALID_IOCTL(ops, VIDIOC_G_SLICED_VBI_CAP, vidioc_g_sliced_vbi_cap);
+       SET_VALID_IOCTL(ops, VIDIOC_LOG_STATUS, vidioc_log_status);
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+       SET_VALID_IOCTL(ops, VIDIOC_DBG_G_REGISTER, vidioc_g_register);
+       SET_VALID_IOCTL(ops, VIDIOC_DBG_S_REGISTER, vidioc_s_register);
+#endif
+       SET_VALID_IOCTL(ops, VIDIOC_DBG_G_CHIP_IDENT, vidioc_g_chip_ident);
+       SET_VALID_IOCTL(ops, VIDIOC_S_HW_FREQ_SEEK, vidioc_s_hw_freq_seek);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals);
+       SET_VALID_IOCTL(ops, VIDIOC_ENUM_DV_PRESETS, vidioc_enum_dv_presets);
+       SET_VALID_IOCTL(ops, VIDIOC_S_DV_PRESET, vidioc_s_dv_preset);
+       SET_VALID_IOCTL(ops, VIDIOC_G_DV_PRESET, vidioc_g_dv_preset);
+       SET_VALID_IOCTL(ops, VIDIOC_QUERY_DV_PRESET, vidioc_query_dv_preset);
+       SET_VALID_IOCTL(ops, VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings);
+       SET_VALID_IOCTL(ops, VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings);
+       /* yes, really vidioc_subscribe_event */
+       SET_VALID_IOCTL(ops, VIDIOC_DQEVENT, vidioc_subscribe_event);
+       SET_VALID_IOCTL(ops, VIDIOC_SUBSCRIBE_EVENT, vidioc_subscribe_event);
+       SET_VALID_IOCTL(ops, VIDIOC_UNSUBSCRIBE_EVENT, vidioc_unsubscribe_event);
+       SET_VALID_IOCTL(ops, VIDIOC_CREATE_BUFS, vidioc_create_bufs);
+       SET_VALID_IOCTL(ops, VIDIOC_PREPARE_BUF, vidioc_prepare_buf);
+       bitmap_andnot(vdev->valid_ioctls, valid_ioctls, vdev->valid_ioctls,
+                       BASE_VIDIOC_PRIVATE);
+}
+
 /**
  *     __video_register_device - register video4linux devices
  *     @vdev: video device structure we want to register
@@ -655,6 +833,9 @@ int __video_register_device(struct video_device *vdev, int type, int nr,
        vdev->index = get_index(vdev);
        mutex_unlock(&videodev_lock);
 
+       if (vdev->ioctl_ops)
+               determine_valid_ioctls(vdev);
+
        /* Part 3: Initialize the character device */
        vdev->cdev = cdev_alloc();
        if (vdev->cdev == NULL) {
index 5b2ec1f..5a508e1 100644 (file)
        memset((u8 *)(p) + offsetof(typeof(*(p)), field) + sizeof((p)->field), \
        0, sizeof(*(p)) - offsetof(typeof(*(p)), field) - sizeof((p)->field))
 
-#define have_fmt_ops(foo) (                                            \
-       ops->vidioc_##foo##_fmt_vid_cap ||                              \
-       ops->vidioc_##foo##_fmt_vid_out ||                              \
-       ops->vidioc_##foo##_fmt_vid_cap_mplane ||                       \
-       ops->vidioc_##foo##_fmt_vid_out_mplane ||                       \
-       ops->vidioc_##foo##_fmt_vid_overlay ||                          \
-       ops->vidioc_##foo##_fmt_vbi_cap ||                              \
-       ops->vidioc_##foo##_fmt_vid_out_overlay ||                      \
-       ops->vidioc_##foo##_fmt_vbi_out ||                              \
-       ops->vidioc_##foo##_fmt_sliced_vbi_cap ||                       \
-       ops->vidioc_##foo##_fmt_sliced_vbi_out ||                       \
-       ops->vidioc_##foo##_fmt_type_private)
-
 struct std_descr {
        v4l2_std_id std;
        const char *descr;
@@ -188,6 +175,7 @@ static const char *v4l2_memory_names[] = {
        [V4L2_MEMORY_MMAP]    = "mmap",
        [V4L2_MEMORY_USERPTR] = "userptr",
        [V4L2_MEMORY_OVERLAY] = "overlay",
+       [V4L2_MEMORY_DMABUF] = "dmabuf",
 };
 
 #define prt_names(a, arr) ((((a) >= 0) && ((a) < ARRAY_SIZE(arr))) ? \
@@ -195,93 +183,116 @@ static const char *v4l2_memory_names[] = {
 
 /* ------------------------------------------------------------------ */
 /* debug help functions                                               */
-static const char *v4l2_ioctls[] = {
-       [_IOC_NR(VIDIOC_QUERYCAP)]         = "VIDIOC_QUERYCAP",
-       [_IOC_NR(VIDIOC_RESERVED)]         = "VIDIOC_RESERVED",
-       [_IOC_NR(VIDIOC_ENUM_FMT)]         = "VIDIOC_ENUM_FMT",
-       [_IOC_NR(VIDIOC_G_FMT)]            = "VIDIOC_G_FMT",
-       [_IOC_NR(VIDIOC_S_FMT)]            = "VIDIOC_S_FMT",
-       [_IOC_NR(VIDIOC_REQBUFS)]          = "VIDIOC_REQBUFS",
-       [_IOC_NR(VIDIOC_QUERYBUF)]         = "VIDIOC_QUERYBUF",
-       [_IOC_NR(VIDIOC_G_FBUF)]           = "VIDIOC_G_FBUF",
-       [_IOC_NR(VIDIOC_S_FBUF)]           = "VIDIOC_S_FBUF",
-       [_IOC_NR(VIDIOC_OVERLAY)]          = "VIDIOC_OVERLAY",
-       [_IOC_NR(VIDIOC_QBUF)]             = "VIDIOC_QBUF",
-       [_IOC_NR(VIDIOC_DQBUF)]            = "VIDIOC_DQBUF",
-       [_IOC_NR(VIDIOC_STREAMON)]         = "VIDIOC_STREAMON",
-       [_IOC_NR(VIDIOC_STREAMOFF)]        = "VIDIOC_STREAMOFF",
-       [_IOC_NR(VIDIOC_G_PARM)]           = "VIDIOC_G_PARM",
-       [_IOC_NR(VIDIOC_S_PARM)]           = "VIDIOC_S_PARM",
-       [_IOC_NR(VIDIOC_G_STD)]            = "VIDIOC_G_STD",
-       [_IOC_NR(VIDIOC_S_STD)]            = "VIDIOC_S_STD",
-       [_IOC_NR(VIDIOC_ENUMSTD)]          = "VIDIOC_ENUMSTD",
-       [_IOC_NR(VIDIOC_ENUMINPUT)]        = "VIDIOC_ENUMINPUT",
-       [_IOC_NR(VIDIOC_G_CTRL)]           = "VIDIOC_G_CTRL",
-       [_IOC_NR(VIDIOC_S_CTRL)]           = "VIDIOC_S_CTRL",
-       [_IOC_NR(VIDIOC_G_TUNER)]          = "VIDIOC_G_TUNER",
-       [_IOC_NR(VIDIOC_S_TUNER)]          = "VIDIOC_S_TUNER",
-       [_IOC_NR(VIDIOC_G_AUDIO)]          = "VIDIOC_G_AUDIO",
-       [_IOC_NR(VIDIOC_S_AUDIO)]          = "VIDIOC_S_AUDIO",
-       [_IOC_NR(VIDIOC_QUERYCTRL)]        = "VIDIOC_QUERYCTRL",
-       [_IOC_NR(VIDIOC_QUERYMENU)]        = "VIDIOC_QUERYMENU",
-       [_IOC_NR(VIDIOC_G_INPUT)]          = "VIDIOC_G_INPUT",
-       [_IOC_NR(VIDIOC_S_INPUT)]          = "VIDIOC_S_INPUT",
-       [_IOC_NR(VIDIOC_G_OUTPUT)]         = "VIDIOC_G_OUTPUT",
-       [_IOC_NR(VIDIOC_S_OUTPUT)]         = "VIDIOC_S_OUTPUT",
-       [_IOC_NR(VIDIOC_ENUMOUTPUT)]       = "VIDIOC_ENUMOUTPUT",
-       [_IOC_NR(VIDIOC_G_AUDOUT)]         = "VIDIOC_G_AUDOUT",
-       [_IOC_NR(VIDIOC_S_AUDOUT)]         = "VIDIOC_S_AUDOUT",
-       [_IOC_NR(VIDIOC_G_MODULATOR)]      = "VIDIOC_G_MODULATOR",
-       [_IOC_NR(VIDIOC_S_MODULATOR)]      = "VIDIOC_S_MODULATOR",
-       [_IOC_NR(VIDIOC_G_FREQUENCY)]      = "VIDIOC_G_FREQUENCY",
-       [_IOC_NR(VIDIOC_S_FREQUENCY)]      = "VIDIOC_S_FREQUENCY",
-       [_IOC_NR(VIDIOC_CROPCAP)]          = "VIDIOC_CROPCAP",
-       [_IOC_NR(VIDIOC_G_CROP)]           = "VIDIOC_G_CROP",
-       [_IOC_NR(VIDIOC_S_CROP)]           = "VIDIOC_S_CROP",
-       [_IOC_NR(VIDIOC_G_SELECTION)]      = "VIDIOC_G_SELECTION",
-       [_IOC_NR(VIDIOC_S_SELECTION)]      = "VIDIOC_S_SELECTION",
-       [_IOC_NR(VIDIOC_G_JPEGCOMP)]       = "VIDIOC_G_JPEGCOMP",
-       [_IOC_NR(VIDIOC_S_JPEGCOMP)]       = "VIDIOC_S_JPEGCOMP",
-       [_IOC_NR(VIDIOC_QUERYSTD)]         = "VIDIOC_QUERYSTD",
-       [_IOC_NR(VIDIOC_TRY_FMT)]          = "VIDIOC_TRY_FMT",
-       [_IOC_NR(VIDIOC_ENUMAUDIO)]        = "VIDIOC_ENUMAUDIO",
-       [_IOC_NR(VIDIOC_ENUMAUDOUT)]       = "VIDIOC_ENUMAUDOUT",
-       [_IOC_NR(VIDIOC_G_PRIORITY)]       = "VIDIOC_G_PRIORITY",
-       [_IOC_NR(VIDIOC_S_PRIORITY)]       = "VIDIOC_S_PRIORITY",
-       [_IOC_NR(VIDIOC_G_SLICED_VBI_CAP)] = "VIDIOC_G_SLICED_VBI_CAP",
-       [_IOC_NR(VIDIOC_LOG_STATUS)]       = "VIDIOC_LOG_STATUS",
-       [_IOC_NR(VIDIOC_G_EXT_CTRLS)]      = "VIDIOC_G_EXT_CTRLS",
-       [_IOC_NR(VIDIOC_S_EXT_CTRLS)]      = "VIDIOC_S_EXT_CTRLS",
-       [_IOC_NR(VIDIOC_TRY_EXT_CTRLS)]    = "VIDIOC_TRY_EXT_CTRLS",
-#if 1
-       [_IOC_NR(VIDIOC_ENUM_FRAMESIZES)]  = "VIDIOC_ENUM_FRAMESIZES",
-       [_IOC_NR(VIDIOC_ENUM_FRAMEINTERVALS)] = "VIDIOC_ENUM_FRAMEINTERVALS",
-       [_IOC_NR(VIDIOC_G_ENC_INDEX)]      = "VIDIOC_G_ENC_INDEX",
-       [_IOC_NR(VIDIOC_ENCODER_CMD)]      = "VIDIOC_ENCODER_CMD",
-       [_IOC_NR(VIDIOC_TRY_ENCODER_CMD)]  = "VIDIOC_TRY_ENCODER_CMD",
-
-       [_IOC_NR(VIDIOC_DECODER_CMD)]      = "VIDIOC_DECODER_CMD",
-       [_IOC_NR(VIDIOC_TRY_DECODER_CMD)]  = "VIDIOC_TRY_DECODER_CMD",
-       [_IOC_NR(VIDIOC_DBG_S_REGISTER)]   = "VIDIOC_DBG_S_REGISTER",
-       [_IOC_NR(VIDIOC_DBG_G_REGISTER)]   = "VIDIOC_DBG_G_REGISTER",
-
-       [_IOC_NR(VIDIOC_DBG_G_CHIP_IDENT)] = "VIDIOC_DBG_G_CHIP_IDENT",
-       [_IOC_NR(VIDIOC_S_HW_FREQ_SEEK)]   = "VIDIOC_S_HW_FREQ_SEEK",
+
+struct v4l2_ioctl_info {
+       unsigned int ioctl;
+       u16 flags;
+       const char * const name;
+};
+
+/* This control needs a priority check */
+#define INFO_FL_PRIO   (1 << 0)
+/* This control can be valid if the filehandle passes a control handler. */
+#define INFO_FL_CTRL   (1 << 1)
+
+#define IOCTL_INFO(_ioctl, _flags) [_IOC_NR(_ioctl)] = {       \
+       .ioctl = _ioctl,                                        \
+       .flags = _flags,                                        \
+       .name = #_ioctl,                                        \
+}
+
+static struct v4l2_ioctl_info v4l2_ioctls[] = {
+       IOCTL_INFO(VIDIOC_QUERYCAP, 0),
+       IOCTL_INFO(VIDIOC_ENUM_FMT, 0),
+       IOCTL_INFO(VIDIOC_G_FMT, 0),
+       IOCTL_INFO(VIDIOC_S_FMT, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_REQBUFS, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_QUERYBUF, 0),
+       IOCTL_INFO(VIDIOC_G_FBUF, 0),
+       IOCTL_INFO(VIDIOC_S_FBUF, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_OVERLAY, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_QBUF, 0),
+       IOCTL_INFO(VIDIOC_EXPBUF, 0),
+       IOCTL_INFO(VIDIOC_DQBUF, 0),
+       IOCTL_INFO(VIDIOC_STREAMON, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_STREAMOFF, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_PARM, 0),
+       IOCTL_INFO(VIDIOC_S_PARM, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_STD, 0),
+       IOCTL_INFO(VIDIOC_S_STD, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_ENUMSTD, 0),
+       IOCTL_INFO(VIDIOC_ENUMINPUT, 0),
+       IOCTL_INFO(VIDIOC_G_CTRL, INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_S_CTRL, INFO_FL_PRIO | INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_G_TUNER, 0),
+       IOCTL_INFO(VIDIOC_S_TUNER, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_AUDIO, 0),
+       IOCTL_INFO(VIDIOC_S_AUDIO, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_QUERYCTRL, INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_QUERYMENU, INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_G_INPUT, 0),
+       IOCTL_INFO(VIDIOC_S_INPUT, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_OUTPUT, 0),
+       IOCTL_INFO(VIDIOC_S_OUTPUT, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_ENUMOUTPUT, 0),
+       IOCTL_INFO(VIDIOC_G_AUDOUT, 0),
+       IOCTL_INFO(VIDIOC_S_AUDOUT, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_MODULATOR, 0),
+       IOCTL_INFO(VIDIOC_S_MODULATOR, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_FREQUENCY, 0),
+       IOCTL_INFO(VIDIOC_S_FREQUENCY, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_CROPCAP, 0),
+       IOCTL_INFO(VIDIOC_G_CROP, 0),
+       IOCTL_INFO(VIDIOC_S_CROP, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_SELECTION, 0),
+       IOCTL_INFO(VIDIOC_S_SELECTION, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_JPEGCOMP, 0),
+       IOCTL_INFO(VIDIOC_S_JPEGCOMP, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_QUERYSTD, 0),
+       IOCTL_INFO(VIDIOC_TRY_FMT, 0),
+       IOCTL_INFO(VIDIOC_ENUMAUDIO, 0),
+       IOCTL_INFO(VIDIOC_ENUMAUDOUT, 0),
+       IOCTL_INFO(VIDIOC_G_PRIORITY, 0),
+       IOCTL_INFO(VIDIOC_S_PRIORITY, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_SLICED_VBI_CAP, 0),
+       IOCTL_INFO(VIDIOC_LOG_STATUS, 0),
+       IOCTL_INFO(VIDIOC_G_EXT_CTRLS, INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_S_EXT_CTRLS, INFO_FL_PRIO | INFO_FL_CTRL),
+       IOCTL_INFO(VIDIOC_TRY_EXT_CTRLS, 0),
+       IOCTL_INFO(VIDIOC_ENUM_FRAMESIZES, 0),
+       IOCTL_INFO(VIDIOC_ENUM_FRAMEINTERVALS, 0),
+       IOCTL_INFO(VIDIOC_G_ENC_INDEX, 0),
+       IOCTL_INFO(VIDIOC_ENCODER_CMD, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_TRY_ENCODER_CMD, 0),
+       IOCTL_INFO(VIDIOC_DECODER_CMD, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_TRY_DECODER_CMD, 0),
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+       IOCTL_INFO(VIDIOC_DBG_S_REGISTER, 0),
+       IOCTL_INFO(VIDIOC_DBG_G_REGISTER, 0),
 #endif
-       [_IOC_NR(VIDIOC_ENUM_DV_PRESETS)]  = "VIDIOC_ENUM_DV_PRESETS",
-       [_IOC_NR(VIDIOC_S_DV_PRESET)]      = "VIDIOC_S_DV_PRESET",
-       [_IOC_NR(VIDIOC_G_DV_PRESET)]      = "VIDIOC_G_DV_PRESET",
-       [_IOC_NR(VIDIOC_QUERY_DV_PRESET)]  = "VIDIOC_QUERY_DV_PRESET",
-       [_IOC_NR(VIDIOC_S_DV_TIMINGS)]     = "VIDIOC_S_DV_TIMINGS",
-       [_IOC_NR(VIDIOC_G_DV_TIMINGS)]     = "VIDIOC_G_DV_TIMINGS",
-       [_IOC_NR(VIDIOC_DQEVENT)]          = "VIDIOC_DQEVENT",
-       [_IOC_NR(VIDIOC_SUBSCRIBE_EVENT)]  = "VIDIOC_SUBSCRIBE_EVENT",
-       [_IOC_NR(VIDIOC_UNSUBSCRIBE_EVENT)] = "VIDIOC_UNSUBSCRIBE_EVENT",
-       [_IOC_NR(VIDIOC_CREATE_BUFS)]      = "VIDIOC_CREATE_BUFS",
-       [_IOC_NR(VIDIOC_PREPARE_BUF)]      = "VIDIOC_PREPARE_BUF",
+       IOCTL_INFO(VIDIOC_DBG_G_CHIP_IDENT, 0),
+       IOCTL_INFO(VIDIOC_S_HW_FREQ_SEEK, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_ENUM_DV_PRESETS, 0),
+       IOCTL_INFO(VIDIOC_S_DV_PRESET, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_DV_PRESET, 0),
+       IOCTL_INFO(VIDIOC_QUERY_DV_PRESET, 0),
+       IOCTL_INFO(VIDIOC_S_DV_TIMINGS, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_G_DV_TIMINGS, 0),
+       IOCTL_INFO(VIDIOC_DQEVENT, 0),
+       IOCTL_INFO(VIDIOC_SUBSCRIBE_EVENT, 0),
+       IOCTL_INFO(VIDIOC_UNSUBSCRIBE_EVENT, 0),
+       IOCTL_INFO(VIDIOC_CREATE_BUFS, INFO_FL_PRIO),
+       IOCTL_INFO(VIDIOC_PREPARE_BUF, 0),
 };
 #define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls)
 
+bool v4l2_is_known_ioctl(unsigned int cmd)
+{
+       if (_IOC_NR(cmd) >= V4L2_IOCTLS)
+               return false;
+       return v4l2_ioctls[_IOC_NR(cmd)].ioctl == cmd;
+}
+
 /* Common ioctl debug function. This function can be used by
    external ioctl messages as well as internal V4L ioctl */
 void v4l_printk_ioctl(unsigned int cmd)
@@ -297,7 +308,7 @@ void v4l_printk_ioctl(unsigned int cmd)
                        type = "v4l2";
                        break;
                }
-               printk("%s", v4l2_ioctls[_IOC_NR(cmd)]);
+               printk("%s", v4l2_ioctls[_IOC_NR(cmd)].name);
                return;
        default:
                type = "unknown";
@@ -504,7 +515,6 @@ static long __video_do_ioctl(struct file *file,
        void *fh = file->private_data;
        struct v4l2_fh *vfh = NULL;
        int use_fh_prio = 0;
-       long ret_prio = 0;
        long ret = -ENOTTY;
 
        if (ops == NULL) {
@@ -513,19 +523,30 @@ static long __video_do_ioctl(struct file *file,
                return ret;
        }
 
-       if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
-                               !(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
-               v4l_print_ioctl(vfd->name, cmd);
-               printk(KERN_CONT "\n");
-       }
-
        if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
                vfh = file->private_data;
                use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
        }
 
-       if (use_fh_prio)
-               ret_prio = v4l2_prio_check(vfd->prio, vfh->prio);
+       if (v4l2_is_known_ioctl(cmd)) {
+               struct v4l2_ioctl_info *info = &v4l2_ioctls[_IOC_NR(cmd)];
+
+               if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
+                   !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
+                       return -ENOTTY;
+
+               if (use_fh_prio && (info->flags & INFO_FL_PRIO)) {
+                       ret = v4l2_prio_check(vfd->prio, vfh->prio);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
+                               !(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
+               v4l_print_ioctl(vfd->name, cmd);
+               printk(KERN_CONT "\n");
+       }
 
        switch (cmd) {
 
@@ -534,9 +555,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_capability *cap = (struct v4l2_capability *)arg;
 
-               if (!ops->vidioc_querycap)
-                       break;
-
                cap->version = LINUX_VERSION_CODE;
                ret = ops->vidioc_querycap(file, fh, cap);
                if (!ret)
@@ -570,14 +588,11 @@ static long __video_do_ioctl(struct file *file,
        {
                enum v4l2_priority *p = arg;
 
-               if (!ops->vidioc_s_priority && !use_fh_prio)
-                       break;
                dbgarg(cmd, "setting priority to %d\n", *p);
                if (ops->vidioc_s_priority)
                        ret = ops->vidioc_s_priority(file, fh, *p);
                else
-                       ret = ret_prio ? ret_prio :
-                               v4l2_prio_change(&vfd->v4l2_dev->prio,
+                       ret = v4l2_prio_change(&vfd->v4l2_dev->prio,
                                                        &vfh->prio, *p);
                break;
        }
@@ -587,6 +602,7 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_fmtdesc *f = arg;
 
+               ret = -EINVAL;
                switch (f->type) {
                case V4L2_BUF_TYPE_VIDEO_CAPTURE:
                        if (likely(ops->vidioc_enum_fmt_vid_cap))
@@ -619,7 +635,7 @@ static long __video_do_ioctl(struct file *file,
                default:
                        break;
                }
-               if (likely (!ret))
+               if (likely(!ret))
                        dbgarg(cmd, "index=%d, type=%d, flags=%d, "
                                "pixelformat=%c%c%c%c, description='%s'\n",
                                f->index, f->type, f->flags,
@@ -628,14 +644,6 @@ static long __video_do_ioctl(struct file *file,
                                (f->pixelformat >> 16) & 0xff,
                                (f->pixelformat >> 24) & 0xff,
                                f->description);
-               else if (ret == -ENOTTY &&
-                        (ops->vidioc_enum_fmt_vid_cap ||
-                         ops->vidioc_enum_fmt_vid_out ||
-                         ops->vidioc_enum_fmt_vid_cap_mplane ||
-                         ops->vidioc_enum_fmt_vid_out_mplane ||
-                         ops->vidioc_enum_fmt_vid_overlay ||
-                         ops->vidioc_enum_fmt_type_private))
-                       ret = -EINVAL;
                break;
        }
        case VIDIOC_G_FMT:
@@ -645,6 +653,7 @@ static long __video_do_ioctl(struct file *file,
                /* FIXME: Should be one dump per type */
                dbgarg(cmd, "type=%s\n", prt_names(f->type, v4l2_type_names));
 
+               ret = -EINVAL;
                switch (f->type) {
                case V4L2_BUF_TYPE_VIDEO_CAPTURE:
                        if (ops->vidioc_g_fmt_vid_cap)
@@ -706,21 +715,12 @@ static long __video_do_ioctl(struct file *file,
                                                                fh, f);
                        break;
                }
-               if (unlikely(ret == -ENOTTY && have_fmt_ops(g)))
-                       ret = -EINVAL;
-
                break;
        }
        case VIDIOC_S_FMT:
        {
                struct v4l2_format *f = (struct v4l2_format *)arg;
 
-               if (!have_fmt_ops(s))
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = -EINVAL;
 
                /* FIXME: Should be one dump per type */
@@ -804,6 +804,7 @@ static long __video_do_ioctl(struct file *file,
                /* FIXME: Should be one dump per type */
                dbgarg(cmd, "type=%s\n", prt_names(f->type,
                                                v4l2_type_names));
+               ret = -EINVAL;
                switch (f->type) {
                case V4L2_BUF_TYPE_VIDEO_CAPTURE:
                        CLEAR_AFTER_FIELD(f, fmt.pix);
@@ -876,8 +877,6 @@ static long __video_do_ioctl(struct file *file,
                                                                fh, f);
                        break;
                }
-               if (unlikely(ret == -ENOTTY && have_fmt_ops(try)))
-                       ret = -EINVAL;
                break;
        }
        /* FIXME: Those buf reqs could be handled here,
@@ -888,12 +887,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_requestbuffers *p = arg;
 
-               if (!ops->vidioc_reqbufs)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = check_fmt(ops, p->type);
                if (ret)
                        break;
@@ -912,8 +905,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_buffer *p = arg;
 
-               if (!ops->vidioc_querybuf)
-                       break;
                ret = check_fmt(ops, p->type);
                if (ret)
                        break;
@@ -927,8 +918,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_buffer *p = arg;
 
-               if (!ops->vidioc_qbuf)
-                       break;
                ret = check_fmt(ops, p->type);
                if (ret)
                        break;
@@ -938,12 +927,15 @@ static long __video_do_ioctl(struct file *file,
                        dbgbuf(cmd, vfd, p);
                break;
        }
+       case VIDIOC_EXPBUF:
+       {
+               ret = ops->vidioc_expbuf(file, fh, arg);
+               break;
+       }
        case VIDIOC_DQBUF:
        {
                struct v4l2_buffer *p = arg;
 
-               if (!ops->vidioc_dqbuf)
-                       break;
                ret = check_fmt(ops, p->type);
                if (ret)
                        break;
@@ -957,12 +949,6 @@ static long __video_do_ioctl(struct file *file,
        {
                int *i = arg;
 
-               if (!ops->vidioc_overlay)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "value=%d\n", *i);
                ret = ops->vidioc_overlay(file, fh, *i);
                break;
@@ -971,8 +957,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_framebuffer *p = arg;
 
-               if (!ops->vidioc_g_fbuf)
-                       break;
                ret = ops->vidioc_g_fbuf(file, fh, arg);
                if (!ret) {
                        dbgarg(cmd, "capability=0x%x, flags=%d, base=0x%08lx\n",
@@ -986,12 +970,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_framebuffer *p = arg;
 
-               if (!ops->vidioc_s_fbuf)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "capability=0x%x, flags=%d, base=0x%08lx\n",
                        p->capability, p->flags, (unsigned long)p->base);
                v4l_print_pix_fmt(vfd, &p->fmt);
@@ -1002,12 +980,6 @@ static long __video_do_ioctl(struct file *file,
        {
                enum v4l2_buf_type i = *(int *)arg;
 
-               if (!ops->vidioc_streamon)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
                ret = ops->vidioc_streamon(file, fh, i);
                break;
@@ -1016,12 +988,6 @@ static long __video_do_ioctl(struct file *file,
        {
                enum v4l2_buf_type i = *(int *)arg;
 
-               if (!ops->vidioc_streamoff)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
                ret = ops->vidioc_streamoff(file, fh, i);
                break;
@@ -1091,13 +1057,6 @@ static long __video_do_ioctl(struct file *file,
 
                dbgarg(cmd, "std=%08Lx\n", (long long unsigned)*id);
 
-               if (!ops->vidioc_s_std)
-                       break;
-
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = -EINVAL;
                norm = (*id) & vfd->tvnorms;
                if (vfd->tvnorms && !norm)      /* Check if std is supported */
@@ -1115,8 +1074,6 @@ static long __video_do_ioctl(struct file *file,
        {
                v4l2_std_id *p = arg;
 
-               if (!ops->vidioc_querystd)
-                       break;
                /*
                 * If nothing detected, it should return all supported
                 * Drivers just need to mask the std argument, in order
@@ -1150,9 +1107,6 @@ static long __video_do_ioctl(struct file *file,
                if (ops->vidioc_s_dv_timings)
                        p->capabilities |= V4L2_IN_CAP_CUSTOM_TIMINGS;
 
-               if (!ops->vidioc_enum_input)
-                       break;
-
                ret = ops->vidioc_enum_input(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "index=%d, name=%s, type=%d, "
@@ -1168,8 +1122,6 @@ static long __video_do_ioctl(struct file *file,
        {
                unsigned int *i = arg;
 
-               if (!ops->vidioc_g_input)
-                       break;
                ret = ops->vidioc_g_input(file, fh, i);
                if (!ret)
                        dbgarg(cmd, "value=%d\n", *i);
@@ -1179,12 +1131,6 @@ static long __video_do_ioctl(struct file *file,
        {
                unsigned int *i = arg;
 
-               if (!ops->vidioc_s_input)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "value=%d\n", *i);
                ret = ops->vidioc_s_input(file, fh, *i);
                break;
@@ -1195,9 +1141,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_output *p = arg;
 
-               if (!ops->vidioc_enum_output)
-                       break;
-
                /*
                 * We set the flags for CAP_PRESETS, CAP_CUSTOM_TIMINGS &
                 * CAP_STD here based on ioctl handler provided by the
@@ -1224,8 +1167,6 @@ static long __video_do_ioctl(struct file *file,
        {
                unsigned int *i = arg;
 
-               if (!ops->vidioc_g_output)
-                       break;
                ret = ops->vidioc_g_output(file, fh, i);
                if (!ret)
                        dbgarg(cmd, "value=%d\n", *i);
@@ -1235,12 +1176,6 @@ static long __video_do_ioctl(struct file *file,
        {
                unsigned int *i = arg;
 
-               if (!ops->vidioc_s_output)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "value=%d\n", *i);
                ret = ops->vidioc_s_output(file, fh, *i);
                break;
@@ -1310,10 +1245,6 @@ static long __video_do_ioctl(struct file *file,
                if (!(vfh && vfh->ctrl_handler) && !vfd->ctrl_handler &&
                        !ops->vidioc_s_ctrl && !ops->vidioc_s_ext_ctrls)
                        break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
 
                dbgarg(cmd, "id=0x%x, value=%d\n", p->id, p->value);
 
@@ -1369,10 +1300,6 @@ static long __video_do_ioctl(struct file *file,
                if (!(vfh && vfh->ctrl_handler) && !vfd->ctrl_handler &&
                                !ops->vidioc_s_ext_ctrls)
                        break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                v4l_print_ext_ctrls(cmd, vfd, p, 1);
                if (vfh && vfh->ctrl_handler)
                        ret = v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, p);
@@ -1428,8 +1355,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audio *p = arg;
 
-               if (!ops->vidioc_enumaudio)
-                       break;
                ret = ops->vidioc_enumaudio(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "index=%d, name=%s, capability=0x%x, "
@@ -1443,9 +1368,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audio *p = arg;
 
-               if (!ops->vidioc_g_audio)
-                       break;
-
                ret = ops->vidioc_g_audio(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "index=%d, name=%s, capability=0x%x, "
@@ -1459,12 +1381,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audio *p = arg;
 
-               if (!ops->vidioc_s_audio)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "index=%d, name=%s, capability=0x%x, "
                                        "mode=0x%x\n", p->index, p->name,
                                        p->capability, p->mode);
@@ -1475,8 +1391,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audioout *p = arg;
 
-               if (!ops->vidioc_enumaudout)
-                       break;
                dbgarg(cmd, "Enum for index=%d\n", p->index);
                ret = ops->vidioc_enumaudout(file, fh, p);
                if (!ret)
@@ -1489,9 +1403,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audioout *p = arg;
 
-               if (!ops->vidioc_g_audout)
-                       break;
-
                ret = ops->vidioc_g_audout(file, fh, p);
                if (!ret)
                        dbgarg2("index=%d, name=%s, capability=%d, "
@@ -1503,12 +1414,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_audioout *p = arg;
 
-               if (!ops->vidioc_s_audout)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "index=%d, name=%s, capability=%d, "
                                        "mode=%d\n", p->index, p->name,
                                        p->capability, p->mode);
@@ -1520,8 +1425,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_modulator *p = arg;
 
-               if (!ops->vidioc_g_modulator)
-                       break;
                ret = ops->vidioc_g_modulator(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "index=%d, name=%s, "
@@ -1536,12 +1439,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_modulator *p = arg;
 
-               if (!ops->vidioc_s_modulator)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "index=%d, name=%s, capability=%d, "
                                "rangelow=%d, rangehigh=%d, txsubchans=%d\n",
                                p->index, p->name, p->capability, p->rangelow,
@@ -1553,9 +1450,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_crop *p = arg;
 
-               if (!ops->vidioc_g_crop && !ops->vidioc_g_selection)
-                       break;
-
                dbgarg(cmd, "type=%s\n", prt_names(p->type, v4l2_type_names));
 
                if (ops->vidioc_g_crop) {
@@ -1587,13 +1481,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_crop *p = arg;
 
-               if (!ops->vidioc_s_crop && !ops->vidioc_s_selection)
-                       break;
-
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "type=%s\n", prt_names(p->type, v4l2_type_names));
                dbgrect(vfd, "", &p->c);
 
@@ -1620,9 +1507,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_selection *p = arg;
 
-               if (!ops->vidioc_g_selection)
-                       break;
-
                dbgarg(cmd, "type=%s\n", prt_names(p->type, v4l2_type_names));
 
                ret = ops->vidioc_g_selection(file, fh, p);
@@ -1634,13 +1518,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_selection *p = arg;
 
-               if (!ops->vidioc_s_selection)
-                       break;
-
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
 
                dbgarg(cmd, "type=%s\n", prt_names(p->type, v4l2_type_names));
                dbgrect(vfd, "", &p->r);
@@ -1653,9 +1530,6 @@ static long __video_do_ioctl(struct file *file,
                struct v4l2_cropcap *p = arg;
 
                /*FIXME: Should also show v4l2_fract pixelaspect */
-               if (!ops->vidioc_cropcap && !ops->vidioc_g_selection)
-                       break;
-
                dbgarg(cmd, "type=%s\n", prt_names(p->type, v4l2_type_names));
                if (ops->vidioc_cropcap) {
                        ret = ops->vidioc_cropcap(file, fh, p);
@@ -1699,9 +1573,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_jpegcompression *p = arg;
 
-               if (!ops->vidioc_g_jpegcomp)
-                       break;
-
                ret = ops->vidioc_g_jpegcomp(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "quality=%d, APPn=%d, "
@@ -1715,12 +1586,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_jpegcompression *p = arg;
 
-               if (!ops->vidioc_g_jpegcomp)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                dbgarg(cmd, "quality=%d, APPn=%d, APP_len=%d, "
                                        "COM_len=%d, jpeg_markers=%d\n",
                                        p->quality, p->APPn, p->APP_len,
@@ -1732,8 +1597,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_enc_idx *p = arg;
 
-               if (!ops->vidioc_g_enc_index)
-                       break;
                ret = ops->vidioc_g_enc_index(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "entries=%d, entries_cap=%d\n",
@@ -1744,12 +1607,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_encoder_cmd *p = arg;
 
-               if (!ops->vidioc_encoder_cmd)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = ops->vidioc_encoder_cmd(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags);
@@ -1759,8 +1616,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_encoder_cmd *p = arg;
 
-               if (!ops->vidioc_try_encoder_cmd)
-                       break;
                ret = ops->vidioc_try_encoder_cmd(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags);
@@ -1770,12 +1625,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_decoder_cmd *p = arg;
 
-               if (!ops->vidioc_decoder_cmd)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = ops->vidioc_decoder_cmd(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags);
@@ -1785,8 +1634,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_decoder_cmd *p = arg;
 
-               if (!ops->vidioc_try_decoder_cmd)
-                       break;
                ret = ops->vidioc_try_decoder_cmd(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags);
@@ -1796,8 +1643,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_streamparm *p = arg;
 
-               if (!ops->vidioc_g_parm && !vfd->current_norm)
-                       break;
                if (ops->vidioc_g_parm) {
                        ret = check_fmt(ops, p->type);
                        if (ret)
@@ -1825,12 +1670,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_streamparm *p = arg;
 
-               if (!ops->vidioc_s_parm)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = check_fmt(ops, p->type);
                if (ret)
                        break;
@@ -1843,9 +1682,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_tuner *p = arg;
 
-               if (!ops->vidioc_g_tuner)
-                       break;
-
                p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
                        V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
                ret = ops->vidioc_g_tuner(file, fh, p);
@@ -1864,12 +1700,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_tuner *p = arg;
 
-               if (!ops->vidioc_s_tuner)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
                        V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
                dbgarg(cmd, "index=%d, name=%s, type=%d, "
@@ -1887,9 +1717,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_frequency *p = arg;
 
-               if (!ops->vidioc_g_frequency)
-                       break;
-
                p->type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
                        V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
                ret = ops->vidioc_g_frequency(file, fh, p);
@@ -1903,12 +1730,6 @@ static long __video_do_ioctl(struct file *file,
                struct v4l2_frequency *p = arg;
                enum v4l2_tuner_type type;
 
-               if (!ops->vidioc_s_frequency)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
                        V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
                dbgarg(cmd, "tuner=%d, type=%d, frequency=%d\n",
@@ -1923,9 +1744,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_sliced_vbi_cap *p = arg;
 
-               if (!ops->vidioc_g_sliced_vbi_cap)
-                       break;
-
                /* Clear up to type, everything after type is zerod already */
                memset(p, 0, offsetof(struct v4l2_sliced_vbi_cap, type));
 
@@ -1937,8 +1755,6 @@ static long __video_do_ioctl(struct file *file,
        }
        case VIDIOC_LOG_STATUS:
        {
-               if (!ops->vidioc_log_status)
-                       break;
                if (vfd->v4l2_dev)
                        pr_info("%s: =================  START STATUS  =================\n",
                                vfd->v4l2_dev->name);
@@ -1948,38 +1764,34 @@ static long __video_do_ioctl(struct file *file,
                                vfd->v4l2_dev->name);
                break;
        }
-#ifdef CONFIG_VIDEO_ADV_DEBUG
        case VIDIOC_DBG_G_REGISTER:
        {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
                struct v4l2_dbg_register *p = arg;
 
-               if (ops->vidioc_g_register) {
-                       if (!capable(CAP_SYS_ADMIN))
-                               ret = -EPERM;
-                       else
-                               ret = ops->vidioc_g_register(file, fh, p);
-               }
+               if (!capable(CAP_SYS_ADMIN))
+                       ret = -EPERM;
+               else
+                       ret = ops->vidioc_g_register(file, fh, p);
+#endif
                break;
        }
        case VIDIOC_DBG_S_REGISTER:
        {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
                struct v4l2_dbg_register *p = arg;
 
-               if (ops->vidioc_s_register) {
-                       if (!capable(CAP_SYS_ADMIN))
-                               ret = -EPERM;
-                       else
-                               ret = ops->vidioc_s_register(file, fh, p);
-               }
+               if (!capable(CAP_SYS_ADMIN))
+                       ret = -EPERM;
+               else
+                       ret = ops->vidioc_s_register(file, fh, p);
+#endif
                break;
        }
-#endif
        case VIDIOC_DBG_G_CHIP_IDENT:
        {
                struct v4l2_dbg_chip_ident *p = arg;
 
-               if (!ops->vidioc_g_chip_ident)
-                       break;
                p->ident = V4L2_IDENT_NONE;
                p->revision = 0;
                ret = ops->vidioc_g_chip_ident(file, fh, p);
@@ -1992,12 +1804,6 @@ static long __video_do_ioctl(struct file *file,
                struct v4l2_hw_freq_seek *p = arg;
                enum v4l2_tuner_type type;
 
-               if (!ops->vidioc_s_hw_freq_seek)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                type = (vfd->vfl_type == VFL_TYPE_RADIO) ?
                        V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
                dbgarg(cmd,
@@ -2013,9 +1819,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_frmsizeenum *p = arg;
 
-               if (!ops->vidioc_enum_framesizes)
-                       break;
-
                ret = ops->vidioc_enum_framesizes(file, fh, p);
                dbgarg(cmd,
                        "index=%d, pixelformat=%c%c%c%c, type=%d ",
@@ -2049,9 +1852,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_frmivalenum *p = arg;
 
-               if (!ops->vidioc_enum_frameintervals)
-                       break;
-
                ret = ops->vidioc_enum_frameintervals(file, fh, p);
                dbgarg(cmd,
                        "index=%d, pixelformat=%d, width=%d, height=%d, type=%d ",
@@ -2084,9 +1884,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_enum_preset *p = arg;
 
-               if (!ops->vidioc_enum_dv_presets)
-                       break;
-
                ret = ops->vidioc_enum_dv_presets(file, fh, p);
                if (!ret)
                        dbgarg(cmd,
@@ -2100,13 +1897,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_preset *p = arg;
 
-               if (!ops->vidioc_s_dv_preset)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
-
                dbgarg(cmd, "preset=%d\n", p->preset);
                ret = ops->vidioc_s_dv_preset(file, fh, p);
                break;
@@ -2115,9 +1905,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_preset *p = arg;
 
-               if (!ops->vidioc_g_dv_preset)
-                       break;
-
                ret = ops->vidioc_g_dv_preset(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "preset=%d\n", p->preset);
@@ -2127,9 +1914,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_preset *p = arg;
 
-               if (!ops->vidioc_query_dv_preset)
-                       break;
-
                ret = ops->vidioc_query_dv_preset(file, fh, p);
                if (!ret)
                        dbgarg(cmd, "preset=%d\n", p->preset);
@@ -2139,13 +1923,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_timings *p = arg;
 
-               if (!ops->vidioc_s_dv_timings)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
-
                switch (p->type) {
                case V4L2_DV_BT_656_1120:
                        dbgarg2("bt-656/1120:interlaced=%d, pixelclock=%lld,"
@@ -2173,9 +1950,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_dv_timings *p = arg;
 
-               if (!ops->vidioc_g_dv_timings)
-                       break;
-
                ret = ops->vidioc_g_dv_timings(file, fh, p);
                if (!ret) {
                        switch (p->type) {
@@ -2207,9 +1981,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_event *ev = arg;
 
-               if (!ops->vidioc_subscribe_event)
-                       break;
-
                ret = v4l2_event_dequeue(fh, ev, file->f_flags & O_NONBLOCK);
                if (ret < 0) {
                        dbgarg(cmd, "no pending events?");
@@ -2226,9 +1997,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_event_subscription *sub = arg;
 
-               if (!ops->vidioc_subscribe_event)
-                       break;
-
                ret = ops->vidioc_subscribe_event(fh, sub);
                if (ret < 0) {
                        dbgarg(cmd, "failed, ret=%ld", ret);
@@ -2241,9 +2009,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_event_subscription *sub = arg;
 
-               if (!ops->vidioc_unsubscribe_event)
-                       break;
-
                ret = ops->vidioc_unsubscribe_event(fh, sub);
                if (ret < 0) {
                        dbgarg(cmd, "failed, ret=%ld", ret);
@@ -2256,12 +2021,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_create_buffers *create = arg;
 
-               if (!ops->vidioc_create_bufs)
-                       break;
-               if (ret_prio) {
-                       ret = ret_prio;
-                       break;
-               }
                ret = check_fmt(ops, create->format.type);
                if (ret)
                        break;
@@ -2275,8 +2034,6 @@ static long __video_do_ioctl(struct file *file,
        {
                struct v4l2_buffer *b = arg;
 
-               if (!ops->vidioc_prepare_buf)
-                       break;
                ret = check_fmt(ops, b->type);
                if (ret)
                        break;
@@ -2289,7 +2046,9 @@ static long __video_do_ioctl(struct file *file,
        default:
                if (!ops->vidioc_default)
                        break;
-               ret = ops->vidioc_default(file, fh, ret_prio >= 0, cmd, arg);
+               ret = ops->vidioc_default(file, fh, use_fh_prio ?
+                               v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
+                               cmd, arg);
                break;
        } /* switch */
 
index 975d0fa..d64811c 100644 (file)
@@ -331,6 +331,19 @@ int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
        return ret;
 }
 EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf);
+/*
+ * v4l2_m2m_expbuf() - multi-queue-not-aware EXPBUF
+ */
+int v4l2_m2m_expbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+                                               struct v4l2_exportbuffer *eb)
+{
+       struct vb2_queue *vq;
+
+       /* Below code is written only for FIMC CAPTURE for now */
+       vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+       return vb2_expbuf(vq, eb);
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_expbuf);
 
 /**
  * v4l2_m2m_qbuf() - enqueue a source or destination buffer, depending on
index de4fa4e..b457c8b 100644 (file)
@@ -335,6 +335,9 @@ static void videobuf_status(struct videobuf_queue *q, struct v4l2_buffer *b,
        case V4L2_MEMORY_OVERLAY:
                b->m.offset  = vb->boff;
                break;
+       case V4L2_MEMORY_DMABUF:
+               /* DMABUF is not handled in videobuf framework */
+               break;
        }
 
        b->flags    = 0;
@@ -411,6 +414,7 @@ int __videobuf_mmap_setup(struct videobuf_queue *q,
                        break;
                case V4L2_MEMORY_USERPTR:
                case V4L2_MEMORY_OVERLAY:
+               case V4L2_MEMORY_DMABUF:
                        /* nothing */
                        break;
                }
index 2e8f1df..982e256 100644 (file)
@@ -105,6 +105,36 @@ static void __vb2_buf_userptr_put(struct vb2_buffer *vb)
        }
 }
 
+/**
+ * __vb2_plane_dmabuf_put() - release memory associated with
+ * a DMABUF shared plane
+ */
+static void __vb2_plane_dmabuf_put(struct vb2_queue *q, struct vb2_plane *p)
+{
+       if (!p->mem_priv)
+               return;
+
+       if (p->dbuf_mapped)
+               call_memop(q, unmap_dmabuf, p->mem_priv);
+
+       call_memop(q, detach_dmabuf, p->mem_priv);
+       dma_buf_put(p->dbuf);
+       memset(p, 0, sizeof *p);
+}
+
+/**
+ * __vb2_buf_dmabuf_put() - release memory associated with
+ * a DMABUF shared buffer
+ */
+static void __vb2_buf_dmabuf_put(struct vb2_buffer *vb)
+{
+       struct vb2_queue *q = vb->vb2_queue;
+       unsigned int plane;
+
+       for (plane = 0; plane < vb->num_planes; ++plane)
+               __vb2_plane_dmabuf_put(q, &vb->planes[plane]);
+}
+
 /**
  * __setup_offsets() - setup unique offsets ("cookies") for every plane in
  * every buffer on the queue
@@ -227,6 +257,8 @@ static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers)
                /* Free MMAP buffers or release USERPTR buffers */
                if (q->memory == V4L2_MEMORY_MMAP)
                        __vb2_buf_mem_free(vb);
+               else if (q->memory == V4L2_MEMORY_DMABUF)
+                       __vb2_buf_dmabuf_put(vb);
                else
                        __vb2_buf_userptr_put(vb);
        }
@@ -349,6 +381,12 @@ static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
                 */
                memcpy(b->m.planes, vb->v4l2_planes,
                        b->length * sizeof(struct v4l2_plane));
+
+               if (q->memory == V4L2_MEMORY_DMABUF) {
+                       unsigned int plane;
+                       for (plane = 0; plane < vb->num_planes; ++plane)
+                               b->m.planes[plane].m.fd = 0;
+               }
        } else {
                /*
                 * We use length and offset in v4l2_planes array even for
@@ -360,6 +398,8 @@ static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
                        b->m.offset = vb->v4l2_planes[0].m.mem_offset;
                else if (q->memory == V4L2_MEMORY_USERPTR)
                        b->m.userptr = vb->v4l2_planes[0].m.userptr;
+               else if (q->memory == V4L2_MEMORY_DMABUF)
+                       b->m.fd = 0;
        }
 
        /*
@@ -450,6 +490,20 @@ static int __verify_mmap_ops(struct vb2_queue *q)
        return 0;
 }
 
+/**
+ * __verify_dmabuf_ops() - verify that all memory operations required for
+ * DMABUF queue type have been provided
+ */
+static int __verify_dmabuf_ops(struct vb2_queue *q)
+{
+       if (!(q->io_modes & VB2_DMABUF) || !q->mem_ops->attach_dmabuf ||
+           !q->mem_ops->detach_dmabuf  || !q->mem_ops->map_dmabuf ||
+           !q->mem_ops->unmap_dmabuf)
+               return -EINVAL;
+
+       return 0;
+}
+
 /**
  * vb2_reqbufs() - Initiate streaming
  * @q:         videobuf2 queue
@@ -483,8 +537,9 @@ int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
                return -EBUSY;
        }
 
-       if (req->memory != V4L2_MEMORY_MMAP
-                       && req->memory != V4L2_MEMORY_USERPTR) {
+       if (req->memory != V4L2_MEMORY_MMAP &&
+           req->memory != V4L2_MEMORY_DMABUF &&
+           req->memory != V4L2_MEMORY_USERPTR) {
                dprintk(1, "reqbufs: unsupported memory type\n");
                return -EINVAL;
        }
@@ -513,6 +568,11 @@ int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
                return -EINVAL;
        }
 
+       if (req->memory == V4L2_MEMORY_DMABUF && __verify_dmabuf_ops(q)) {
+               dprintk(1, "reqbufs: DMABUF for current setup unsupported\n");
+               return -EINVAL;
+       }
+
        if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
                /*
                 * We already have buffers allocated, so first check if they
@@ -619,8 +679,9 @@ int vb2_create_bufs(struct vb2_queue *q, struct v4l2_create_buffers *create)
                return -EBUSY;
        }
 
-       if (create->memory != V4L2_MEMORY_MMAP
-                       && create->memory != V4L2_MEMORY_USERPTR) {
+       if (create->memory != V4L2_MEMORY_MMAP &&
+           create->memory != V4L2_MEMORY_USERPTR &&
+           create->memory != V4L2_MEMORY_DMABUF) {
                dprintk(1, "%s(): unsupported memory type\n", __func__);
                return -EINVAL;
        }
@@ -644,6 +705,11 @@ int vb2_create_bufs(struct vb2_queue *q, struct v4l2_create_buffers *create)
                return -EINVAL;
        }
 
+       if (create->memory == V4L2_MEMORY_DMABUF && __verify_dmabuf_ops(q)) {
+               dprintk(1, "%s(): DMABUF for current setup unsupported\n", __func__);
+               return -EINVAL;
+       }
+
        if (q->num_buffers == VIDEO_MAX_FRAME) {
                dprintk(1, "%s(): maximum number of buffers already allocated\n",
                        __func__);
@@ -776,6 +842,7 @@ void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
 {
        struct vb2_queue *q = vb->vb2_queue;
        unsigned long flags;
+       unsigned int plane;
 
        if (vb->state != VB2_BUF_STATE_ACTIVE)
                return;
@@ -786,6 +853,10 @@ void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
        dprintk(4, "Done processing on buffer %d, state: %d\n",
                        vb->v4l2_buf.index, vb->state);
 
+       /* sync buffers */
+       for (plane = 0; plane < vb->num_planes; ++plane)
+               call_memop(q, finish, vb->planes[plane].mem_priv);
+
        /* Add the buffer to the done buffers list */
        spin_lock_irqsave(&q->done_lock, flags);
        vb->state = state;
@@ -839,6 +910,14 @@ static int __fill_vb2_buffer(struct vb2_buffer *vb, const struct v4l2_buffer *b,
                                        b->m.planes[plane].length;
                        }
                }
+               if (b->memory == V4L2_MEMORY_DMABUF) {
+                       for (plane = 0; plane < vb->num_planes; ++plane) {
+                               v4l2_planes[plane].bytesused =
+                                       b->m.planes[plane].bytesused;
+                               v4l2_planes[plane].m.fd =
+                                       b->m.planes[plane].m.fd;
+                       }
+               }
        } else {
                /*
                 * Single-planar buffers do not use planes array,
@@ -853,6 +932,10 @@ static int __fill_vb2_buffer(struct vb2_buffer *vb, const struct v4l2_buffer *b,
                        v4l2_planes[0].m.userptr = b->m.userptr;
                        v4l2_planes[0].length = b->length;
                }
+
+               if (b->memory == V4L2_MEMORY_DMABUF)
+                       v4l2_planes[0].m.fd = b->m.fd;
+
        }
 
        vb->v4l2_buf.field = b->field;
@@ -956,15 +1039,115 @@ static int __qbuf_mmap(struct vb2_buffer *vb, const struct v4l2_buffer *b)
        return __fill_vb2_buffer(vb, b, vb->v4l2_planes);
 }
 
+/**
+ * __qbuf_dmabuf() - handle qbuf of a DMABUF buffer
+ */
+static int __qbuf_dmabuf(struct vb2_buffer *vb, const struct v4l2_buffer *b)
+{
+       struct v4l2_plane planes[VIDEO_MAX_PLANES];
+       struct vb2_queue *q = vb->vb2_queue;
+       void *mem_priv;
+       unsigned int plane;
+       int ret;
+       int write = !V4L2_TYPE_IS_OUTPUT(q->type);
+
+       /* Verify and copy relevant information provided by the userspace */
+       ret = __fill_vb2_buffer(vb, b, planes);
+       if (ret)
+               return ret;
+
+       for (plane = 0; plane < vb->num_planes; ++plane) {
+               struct dma_buf *dbuf = dma_buf_get(planes[plane].m.fd);
+
+               if (IS_ERR_OR_NULL(dbuf)) {
+                       dprintk(1, "qbuf: invalid dmabuf fd for "
+                               "plane %d\n", plane);
+                       ret = -EINVAL;
+                       goto err;
+               }
+
+               /* Skip the plane if already verified */
+               if (dbuf == vb->planes[plane].dbuf) {
+                       planes[plane].length = dbuf->size;
+                       dma_buf_put(dbuf);
+                       continue;
+               }
+
+               dprintk(3, "qbuf: buffer description for plane %d changed, "
+                       "reattaching dma buf\n", plane);
+
+               /* Release previously acquired memory if present */
+               __vb2_plane_dmabuf_put(q, &vb->planes[plane]);
+
+               /* Acquire each plane's memory */
+               mem_priv = call_memop(q, attach_dmabuf, q->alloc_ctx[plane],
+                       dbuf, q->plane_sizes[plane], write);
+               if (IS_ERR(mem_priv)) {
+                       dprintk(1, "qbuf: failed acquiring dmabuf "
+                               "memory for plane %d\n", plane);
+                       ret = PTR_ERR(mem_priv);
+                       goto err;
+               }
+
+               planes[plane].length = dbuf->size;
+               vb->planes[plane].dbuf = dbuf;
+               vb->planes[plane].mem_priv = mem_priv;
+       }
+
+       /* TODO: This pins the buffer(s) with  dma_buf_map_attachment()).. but
+        * really we want to do this just before the DMA, not while queueing
+        * the buffer(s)..
+        */
+       for (plane = 0; plane < vb->num_planes; ++plane) {
+               ret = call_memop(q, map_dmabuf, vb->planes[plane].mem_priv);
+               if (ret) {
+                       dprintk(1, "qbuf: failed mapping dmabuf "
+                               "memory for plane %d\n", plane);
+                       goto err;
+               }
+               vb->planes[plane].dbuf_mapped = 1;
+       }
+
+       /*
+        * Call driver-specific initialization on the newly acquired buffer,
+        * if provided.
+        */
+       ret = call_qop(q, buf_init, vb);
+       if (ret) {
+               dprintk(1, "qbuf: buffer initialization failed\n");
+               goto err;
+       }
+
+       /*
+        * Now that everything is in order, copy relevant information
+        * provided by userspace.
+        */
+       for (plane = 0; plane < vb->num_planes; ++plane)
+               vb->v4l2_planes[plane] = planes[plane];
+
+       return 0;
+err:
+       /* In case of errors, release planes that were already acquired */
+       __vb2_buf_dmabuf_put(vb);
+
+       return ret;
+}
+
 /**
  * __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing
  */
 static void __enqueue_in_driver(struct vb2_buffer *vb)
 {
        struct vb2_queue *q = vb->vb2_queue;
+       unsigned int plane;
 
        vb->state = VB2_BUF_STATE_ACTIVE;
        atomic_inc(&q->queued_count);
+
+       /* sync buffers */
+       for (plane = 0; plane < vb->num_planes; ++plane)
+               call_memop(q, prepare, vb->planes[plane].mem_priv);
+
        q->ops->buf_queue(vb);
 }
 
@@ -980,6 +1163,9 @@ static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b)
        case V4L2_MEMORY_USERPTR:
                ret = __qbuf_userptr(vb, b);
                break;
+       case V4L2_MEMORY_DMABUF:
+               ret = __qbuf_dmabuf(vb, b);
+               break;
        default:
                WARN(1, "Invalid queue type\n");
                ret = -EINVAL;
@@ -1335,6 +1521,19 @@ int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
                return ret;
        }
 
+       /* TODO: this unpins the buffer(dma_buf_unmap_attachment()).. but
+        * really we want to do this just after DMA, not when the
+        * buffer is dequeued..
+        */
+       if (q->memory == V4L2_MEMORY_DMABUF) {
+               unsigned int i;
+
+               for (i = 0; i < vb->num_planes; ++i) {
+                       call_memop(q, unmap_dmabuf, vb->planes[i].mem_priv);
+                       vb->planes[i].dbuf_mapped = 0;
+               }
+       }
+
        switch (vb->state) {
        case VB2_BUF_STATE_DONE:
                dprintk(3, "dqbuf: Returning done buffer\n");
@@ -1527,6 +1726,73 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long off,
        return -EINVAL;
 }
 
+/**
+ * vb2_expbuf() - Export a buffer as a file descriptor
+ * @q:         videobuf2 queue
+ * @eb:                export buffer structure passed from userspace to vidioc_expbuf
+ *             handler in driver
+ *
+ * The return values from this function are intended to be directly returned
+ * from vidioc_expbuf handler in driver.
+ */
+int vb2_expbuf(struct vb2_queue *q, struct v4l2_exportbuffer *eb)
+{
+       struct vb2_buffer *vb = NULL;
+       struct vb2_plane *vb_plane;
+       unsigned int buffer, plane;
+       int ret;
+       struct dma_buf *dbuf;
+
+       if (q->memory != V4L2_MEMORY_MMAP) {
+               dprintk(1, "Queue is not currently set up for mmap\n");
+               return -EINVAL;
+       }
+
+       if (!q->mem_ops->get_dmabuf) {
+               dprintk(1, "Queue does not support DMA buffer exporting\n");
+               return -EINVAL;
+       }
+
+       if (eb->flags & ~O_CLOEXEC) {
+               dprintk(1, "Queue does support only O_CLOEXEC flag\n");
+               return -EINVAL;
+       }
+
+       /*
+        * Find the plane corresponding to the offset passed by userspace.
+        */
+       ret = __find_plane_by_offset(q, eb->mem_offset, &buffer, &plane);
+       if (ret) {
+               dprintk(1, "invalid offset %u\n", eb->mem_offset);
+               return ret;
+       }
+
+       vb = q->bufs[buffer];
+       vb_plane = &vb->planes[plane];
+
+       dbuf = call_memop(q, get_dmabuf, vb_plane->mem_priv);
+       if (IS_ERR_OR_NULL(dbuf)) {
+               dprintk(1, "Failed to export buffer %d, plane %d\n",
+                       buffer, plane);
+               return -EINVAL;
+       }
+
+       ret = dma_buf_fd(dbuf, eb->flags);
+       if (ret < 0) {
+               dprintk(3, "buffer %d, plane %d failed to export (%d)\n",
+                       buffer, plane, ret);
+               dma_buf_put(dbuf);
+               return ret;
+       }
+
+       dprintk(3, "buffer %d, plane %d exported as %d descriptor\n",
+               buffer, plane, ret);
+       eb->fd = ret;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(vb2_expbuf);
+
 /**
  * vb2_mmap() - map video buffers into application address space
  * @q:         videobuf2 queue
index 4b71326..34ae4bd 100644 (file)
  * the Free Software Foundation.
  */
 
+#include <linux/dma-buf.h>
 #include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/dma-mapping.h>
 
@@ -23,40 +26,153 @@ struct vb2_dc_conf {
 };
 
 struct vb2_dc_buf {
-       struct vb2_dc_conf              *conf;
+       struct device                   *dev;
        void                            *vaddr;
-       dma_addr_t                      dma_addr;
        unsigned long                   size;
-       struct vm_area_struct           *vma;
-       atomic_t                        refcount;
+       dma_addr_t                      dma_addr;
+       enum dma_data_direction         dma_dir;
+       struct sg_table                 *dma_sgt;
+
+       /* MMAP related */
        struct vb2_vmarea_handler       handler;
+       atomic_t                        refcount;
+       struct sg_table                 *sgt_base;
+
+       /* USERPTR related */
+       struct vm_area_struct           *vma;
+
+       /* DMABUF related */
+       struct dma_buf_attachment       *db_attach;
 };
 
-static void vb2_dma_contig_put(void *buf_priv);
+/*********************************************/
+/*        scatterlist table functions        */
+/*********************************************/
+
+
+static void vb2_dc_sgt_foreach_page(struct sg_table *sgt,
+       void (*cb)(struct page *pg))
+{
+       struct scatterlist *s;
+       unsigned int i;
+
+       for_each_sg(sgt->sgl, s, sgt->orig_nents, i) {
+               struct page *page = sg_page(s);
+               unsigned int n_pages = PAGE_ALIGN(s->offset + s->length)
+                       >> PAGE_SHIFT;
+               unsigned int j;
+
+               for (j = 0; j < n_pages; ++j, ++page)
+                       cb(page);
+       }
+}
+
+static unsigned long vb2_dc_get_contiguous_size(struct sg_table *sgt)
+{
+       struct scatterlist *s;
+       dma_addr_t expected = sg_dma_address(sgt->sgl);
+       unsigned int i;
+       unsigned long size = 0;
+
+       for_each_sg(sgt->sgl, s, sgt->nents, i) {
+               if (sg_dma_address(s) != expected)
+                       break;
+               expected = sg_dma_address(s) + sg_dma_len(s);
+               size += sg_dma_len(s);
+       }
+       return size;
+}
+
+/*********************************************/
+/*         callbacks for all buffers         */
+/*********************************************/
+
+static void *vb2_dc_cookie(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+
+       return &buf->dma_addr;
+}
+
+static void *vb2_dc_vaddr(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+
+       return buf->vaddr;
+}
+
+static unsigned int vb2_dc_num_users(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+
+       return atomic_read(&buf->refcount);
+}
+
+static void vb2_dc_prepare(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+       struct sg_table *sgt = buf->dma_sgt;
+
+       /* DMABUF exporter will flush the cache for us */
+       if (!sgt || buf->db_attach)
+               return;
+
+       dma_sync_sg_for_device(buf->dev, sgt->sgl, sgt->nents, buf->dma_dir);
+}
+
+static void vb2_dc_finish(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+       struct sg_table *sgt = buf->dma_sgt;
+
+       /* DMABUF exporter will flush the cache for us */
+       if (!sgt || buf->db_attach)
+               return;
+
+       dma_sync_sg_for_cpu(buf->dev, sgt->sgl, sgt->nents, buf->dma_dir);
+}
+
+/*********************************************/
+/*        callbacks for MMAP buffers         */
+/*********************************************/
+
+static void vb2_dc_put(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+
+       if (!atomic_dec_and_test(&buf->refcount))
+               return;
+
+       if (buf->sgt_base) {
+               sg_free_table(buf->sgt_base);
+               kfree(buf->sgt_base);
+       }
+       dma_free_coherent(buf->dev, buf->size, buf->vaddr, buf->dma_addr);
+       kfree(buf);
+}
 
-static void *vb2_dma_contig_alloc(void *alloc_ctx, unsigned long size)
+static void *vb2_dc_alloc(void *alloc_ctx, unsigned long size)
 {
        struct vb2_dc_conf *conf = alloc_ctx;
+       struct device *dev = conf->dev;
        struct vb2_dc_buf *buf;
 
        buf = kzalloc(sizeof *buf, GFP_KERNEL);
        if (!buf)
                return ERR_PTR(-ENOMEM);
 
-       buf->vaddr = dma_alloc_coherent(conf->dev, size, &buf->dma_addr,
-                                       GFP_KERNEL);
+       buf->vaddr = dma_alloc_coherent(dev, size, &buf->dma_addr, GFP_KERNEL);
        if (!buf->vaddr) {
-               dev_err(conf->dev, "dma_alloc_coherent of size %ld failed\n",
-                       size);
+               dev_err(dev, "dma_alloc_coherent of size %ld failed\n", size);
                kfree(buf);
                return ERR_PTR(-ENOMEM);
        }
 
-       buf->conf = conf;
+       buf->dev = dev;
        buf->size = size;
 
        buf->handler.refcount = &buf->refcount;
-       buf->handler.put = vb2_dma_contig_put;
+       buf->handler.put = vb2_dc_put;
        buf->handler.arg = buf;
 
        atomic_inc(&buf->refcount);
@@ -64,100 +180,564 @@ static void *vb2_dma_contig_alloc(void *alloc_ctx, unsigned long size)
        return buf;
 }
 
-static void vb2_dma_contig_put(void *buf_priv)
+static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
 {
        struct vb2_dc_buf *buf = buf_priv;
+       int ret;
 
-       if (atomic_dec_and_test(&buf->refcount)) {
-               dma_free_coherent(buf->conf->dev, buf->size, buf->vaddr,
-                                 buf->dma_addr);
-               kfree(buf);
+       if (!buf) {
+               printk(KERN_ERR "No buffer to map\n");
+               return -EINVAL;
+       }
+
+       /*
+        * dma_mmap_* uses vm_pgoff as in-buffer offset, but we want to
+        * map whole buffer
+        */
+       vma->vm_pgoff = 0;
+
+       ret = dma_mmap_coherent(buf->dev, vma, buf->vaddr,
+               buf->dma_addr, buf->size);
+
+       if (ret) {
+               printk(KERN_ERR "Remapping memory failed, error: %d\n", ret);
+               return ret;
        }
+
+       vma->vm_flags           |= VM_DONTEXPAND | VM_RESERVED;
+       vma->vm_private_data    = &buf->handler;
+       vma->vm_ops             = &vb2_common_vm_ops;
+
+       vma->vm_ops->open(vma);
+
+       printk(KERN_DEBUG "%s: mapped dma addr 0x%08lx at 0x%08lx, size %ld\n",
+               __func__, (unsigned long)buf->dma_addr, vma->vm_start,
+               buf->size);
+
+       return 0;
 }
 
-static void *vb2_dma_contig_cookie(void *buf_priv)
+/*********************************************/
+/*         DMABUF ops for exporters          */
+/*********************************************/
+
+struct vb2_dc_attachment {
+       struct sg_table sgt;
+       enum dma_data_direction dir;
+};
+
+static int vb2_dc_dmabuf_ops_attach(struct dma_buf *dbuf, struct device *dev,
+       struct dma_buf_attachment *dbuf_attach)
 {
-       struct vb2_dc_buf *buf = buf_priv;
+       struct vb2_dc_attachment *attach;
+       unsigned int i;
+       struct scatterlist *rd, *wr;
+       struct sg_table *sgt;
+       struct vb2_dc_buf *buf = dbuf->priv;
+       int ret;
 
-       return &buf->dma_addr;
+       attach = kzalloc(sizeof *attach, GFP_KERNEL);
+       if (!attach)
+               return -ENOMEM;
+
+       sgt = &attach->sgt;
+       /* Copy the buf->base_sgt scatter list to the attachment, as we can't
+        * map the same scatter list to multiple attachments at the same time.
+        */
+       ret = sg_alloc_table(sgt, buf->sgt_base->orig_nents, GFP_KERNEL);
+       if (ret) {
+               kfree(attach);
+               return -ENOMEM;
+       }
+
+       rd = buf->sgt_base->sgl;
+       wr = sgt->sgl;
+       for (i = 0; i < sgt->orig_nents; ++i) {
+               sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
+               rd = sg_next(rd);
+               wr = sg_next(wr);
+       }
+
+       attach->dir = DMA_NONE;
+       dbuf_attach->priv = attach;
+
+       return 0;
 }
 
-static void *vb2_dma_contig_vaddr(void *buf_priv)
+static void vb2_dc_dmabuf_ops_detach(struct dma_buf *dbuf,
+       struct dma_buf_attachment *db_attach)
 {
-       struct vb2_dc_buf *buf = buf_priv;
-       if (!buf)
-               return NULL;
+       struct vb2_dc_attachment *attach = db_attach->priv;
+       struct sg_table *sgt;
+
+       if (!attach)
+               return;
+
+       sgt = &attach->sgt;
+
+       /* release the scatterlist cache */
+       if (attach->dir != DMA_NONE)
+               dma_unmap_sg(db_attach->dev, sgt->sgl, sgt->orig_nents,
+                       attach->dir);
+       sg_free_table(sgt);
+       kfree(attach);
+       db_attach->priv = NULL;
+}
+
+static struct sg_table *vb2_dc_dmabuf_ops_map(
+       struct dma_buf_attachment *db_attach, enum dma_data_direction dir)
+{
+       struct vb2_dc_attachment *attach = db_attach->priv;
+       /* stealing dmabuf mutex to serialize map/unmap operations */
+       struct mutex *lock = &db_attach->dmabuf->lock;
+       struct sg_table *sgt;
+       int ret;
+
+       mutex_lock(lock);
+
+       sgt = &attach->sgt;
+       /* return previously mapped sg table */
+       if (attach->dir == dir) {
+               mutex_unlock(lock);
+               return sgt;
+       }
+
+       /* release any previous cache */
+       if (attach->dir != DMA_NONE) {
+               dma_unmap_sg(db_attach->dev, sgt->sgl, sgt->orig_nents,
+                       attach->dir);
+               attach->dir = DMA_NONE;
+       }
+
+       /* mapping to the client with new direction */
+       ret = dma_map_sg(db_attach->dev, sgt->sgl, sgt->orig_nents, dir);
+       if (ret <= 0) {
+               printk(KERN_ERR "failed to map scatterlist\n");
+               mutex_unlock(lock);
+               return ERR_PTR(-EIO);
+       }
+
+       attach->dir = dir;
+
+       mutex_unlock(lock);
+
+       return sgt;
+}
+
+static void vb2_dc_dmabuf_ops_unmap(struct dma_buf_attachment *db_attach,
+       struct sg_table *sgt, enum dma_data_direction dir)
+{
+       /* nothing to be done here */
+}
+
+static void vb2_dc_dmabuf_ops_release(struct dma_buf *dbuf)
+{
+       /* drop reference obtained in vb2_dc_get_dmabuf */
+       vb2_dc_put(dbuf->priv);
+}
+
+static void *vb2_dc_dmabuf_ops_kmap(struct dma_buf *dbuf, unsigned long pgnum)
+{
+       struct vb2_dc_buf *buf = dbuf->priv;
+
+       return buf->vaddr + pgnum * PAGE_SIZE;
+}
+
+static void *vb2_dc_dmabuf_ops_vmap(struct dma_buf *dbuf)
+{
+       struct vb2_dc_buf *buf = dbuf->priv;
 
        return buf->vaddr;
 }
 
-static unsigned int vb2_dma_contig_num_users(void *buf_priv)
+/* a dummy function to support the mmap functionality for now */
+static void *vb2_dc_dmabuf_ops_mmap(struct dma_buf *dbuf)
 {
-       struct vb2_dc_buf *buf = buf_priv;
+       /* do nothing */
+       return NULL;
+}
 
-       return atomic_read(&buf->refcount);
+static struct dma_buf_ops vb2_dc_dmabuf_ops = {
+       .attach = vb2_dc_dmabuf_ops_attach,
+       .detach = vb2_dc_dmabuf_ops_detach,
+       .map_dma_buf = vb2_dc_dmabuf_ops_map,
+       .unmap_dma_buf = vb2_dc_dmabuf_ops_unmap,
+       .kmap = vb2_dc_dmabuf_ops_kmap,
+       .kmap_atomic = vb2_dc_dmabuf_ops_kmap,
+       .vmap = vb2_dc_dmabuf_ops_vmap,
+       .mmap = vb2_dc_dmabuf_ops_mmap,
+       .release = vb2_dc_dmabuf_ops_release,
+};
+
+static struct sg_table *vb2_dc_get_base_sgt(struct vb2_dc_buf *buf)
+{
+       int ret;
+       struct sg_table *sgt;
+
+       sgt = kmalloc(sizeof *sgt, GFP_KERNEL);
+       if (!sgt) {
+               dev_err(buf->dev, "failed to alloc sg table\n");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       ret = dma_get_sgtable(buf->dev, sgt, buf->vaddr, buf->dma_addr,
+               buf->size);
+       if (ret < 0) {
+               dev_err(buf->dev, "failed to get scatterlist from DMA API\n");
+               kfree(sgt);
+               return ERR_PTR(ret);
+       }
+
+       return sgt;
 }
 
-static int vb2_dma_contig_mmap(void *buf_priv, struct vm_area_struct *vma)
+static struct dma_buf *vb2_dc_get_dmabuf(void *buf_priv)
 {
        struct vb2_dc_buf *buf = buf_priv;
+       struct dma_buf *dbuf;
+       struct sg_table *sgt = buf->sgt_base;
 
-       if (!buf) {
-               printk(KERN_ERR "No buffer to map\n");
-               return -EINVAL;
+       if (!sgt)
+               sgt = vb2_dc_get_base_sgt(buf);
+       if (WARN_ON(IS_ERR(sgt)))
+               return NULL;
+
+       /* cache base sgt for future use */
+       buf->sgt_base = sgt;
+
+       dbuf = dma_buf_export(buf, &vb2_dc_dmabuf_ops, buf->size, 0);
+       if (IS_ERR(dbuf))
+               return NULL;
+
+       /* dmabuf keeps reference to vb2 buffer */
+       atomic_inc(&buf->refcount);
+
+       return dbuf;
+}
+
+/*********************************************/
+/*       callbacks for USERPTR buffers       */
+/*********************************************/
+
+static inline int vma_is_io(struct vm_area_struct *vma)
+{
+       return !!(vma->vm_flags & (VM_IO | VM_PFNMAP));
+}
+
+static int vb2_dc_get_user_pages(unsigned long start, struct page **pages,
+       int n_pages, struct vm_area_struct *vma, int write)
+{
+       if (vma_is_io(vma)) {
+               unsigned int i;
+
+               for (i = 0; i < n_pages; ++i, start += PAGE_SIZE) {
+                       unsigned long pfn;
+                       int ret = follow_pfn(vma, start, &pfn);
+
+                       if (ret) {
+                               printk(KERN_ERR "no page for address %lu\n",
+                                       start);
+                               return ret;
+                       }
+                       pages[i] = pfn_to_page(pfn);
+               }
+       } else {
+               int n;
+
+               n = get_user_pages(current, current->mm, start & PAGE_MASK,
+                       n_pages, write, 1, pages, NULL);
+               /* negative error means that no page was pinned */
+               n = max(n, 0);
+               if (n != n_pages) {
+                       printk(KERN_ERR "got only %d of %d user pages\n",
+                               n, n_pages);
+                       while (n)
+                               put_page(pages[--n]);
+                       return -EFAULT;
+               }
        }
 
-       return vb2_mmap_pfn_range(vma, buf->dma_addr, buf->size,
-                                 &vb2_common_vm_ops, &buf->handler);
+       return 0;
 }
 
-static void *vb2_dma_contig_get_userptr(void *alloc_ctx, unsigned long vaddr,
-                                       unsigned long size, int write)
+static void vb2_dc_put_dirty_page(struct page *page)
 {
+       set_page_dirty_lock(page);
+       put_page(page);
+}
+
+static void vb2_dc_put_userptr(void *buf_priv)
+{
+       struct vb2_dc_buf *buf = buf_priv;
+       struct sg_table *sgt = buf->dma_sgt;
+
+       dma_unmap_sg(buf->dev, sgt->sgl, sgt->orig_nents, buf->dma_dir);
+       if (!vma_is_io(buf->vma))
+               vb2_dc_sgt_foreach_page(sgt, vb2_dc_put_dirty_page);
+
+       sg_free_table(sgt);
+       kfree(sgt);
+       vb2_put_vma(buf->vma);
+       kfree(buf);
+}
+
+static void *vb2_dc_get_userptr(void *alloc_ctx, unsigned long vaddr,
+       unsigned long size, int write)
+{
+       struct vb2_dc_conf *conf = alloc_ctx;
        struct vb2_dc_buf *buf;
+       unsigned long start;
+       unsigned long end;
+       unsigned long offset;
+       struct page **pages;
+       int n_pages;
+       int ret = 0;
        struct vm_area_struct *vma;
-       dma_addr_t dma_addr = 0;
-       int ret;
+       struct sg_table *sgt;
+       unsigned long contig_size;
 
        buf = kzalloc(sizeof *buf, GFP_KERNEL);
        if (!buf)
                return ERR_PTR(-ENOMEM);
 
-       ret = vb2_get_contig_userptr(vaddr, size, &vma, &dma_addr);
+       buf->dev = conf->dev;
+       buf->dma_dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+       start = vaddr & PAGE_MASK;
+       offset = vaddr & ~PAGE_MASK;
+       end = PAGE_ALIGN(vaddr + size);
+       n_pages = (end - start) >> PAGE_SHIFT;
+
+       pages = kmalloc(n_pages * sizeof pages[0], GFP_KERNEL);
+       if (!pages) {
+               ret = -ENOMEM;
+               printk(KERN_ERR "failed to allocate pages table\n");
+               goto fail_buf;
+       }
+
+       /* current->mm->mmap_sem is taken by videobuf2 core */
+       vma = find_vma(current->mm, vaddr);
+       if (!vma) {
+               printk(KERN_ERR "no vma for address %lu\n", vaddr);
+               ret = -EFAULT;
+               goto fail_pages;
+       }
+
+       if (vma->vm_end < vaddr + size) {
+               printk(KERN_ERR "vma at %lu is too small for %lu bytes\n",
+                       vaddr, size);
+               ret = -EFAULT;
+               goto fail_pages;
+       }
+
+       buf->vma = vb2_get_vma(vma);
+       if (!buf->vma) {
+               printk(KERN_ERR "failed to copy vma\n");
+               ret = -ENOMEM;
+               goto fail_pages;
+       }
+
+       /* extract page list from userspace mapping */
+       ret = vb2_dc_get_user_pages(start, pages, n_pages, vma, write);
        if (ret) {
-               printk(KERN_ERR "Failed acquiring VMA for vaddr 0x%08lx\n",
-                               vaddr);
-               kfree(buf);
-               return ERR_PTR(ret);
+               printk(KERN_ERR "failed to get user pages\n");
+               goto fail_vma;
+       }
+
+       sgt = kzalloc(sizeof *sgt, GFP_KERNEL);
+       if (!sgt) {
+               printk(KERN_ERR "failed to allocate sg table\n");
+               ret = -ENOMEM;
+               goto fail_get_user_pages;
+       }
+
+       ret = sg_alloc_table_from_pages(sgt, pages, n_pages,
+               offset, size, GFP_KERNEL);
+       if (ret) {
+               printk(KERN_ERR "failed to initialize sg table\n");
+               goto fail_sgt;
+       }
+
+       /* pages are no longer needed */
+       kfree(pages);
+       pages = NULL;
+
+       sgt->nents = dma_map_sg(buf->dev, sgt->sgl, sgt->orig_nents,
+               buf->dma_dir);
+       if (sgt->nents <= 0) {
+               printk(KERN_ERR "failed to map scatterlist\n");
+               ret = -EIO;
+               goto fail_sgt_init;
+       }
+
+       contig_size = vb2_dc_get_contiguous_size(sgt);
+       if (contig_size < size) {
+               printk(KERN_ERR "contiguous mapping is too small %lu/%lu\n",
+                       contig_size, size);
+               ret = -EFAULT;
+               goto fail_map_sg;
        }
 
+       buf->dma_addr = sg_dma_address(sgt->sgl);
        buf->size = size;
-       buf->dma_addr = dma_addr;
-       buf->vma = vma;
+       buf->dma_sgt = sgt;
 
        return buf;
+
+fail_map_sg:
+       dma_unmap_sg(buf->dev, sgt->sgl, sgt->orig_nents, buf->dma_dir);
+
+fail_sgt_init:
+       if (!vma_is_io(buf->vma))
+               vb2_dc_sgt_foreach_page(sgt, put_page);
+       sg_free_table(sgt);
+
+fail_sgt:
+       kfree(sgt);
+
+fail_get_user_pages:
+       if (pages && !vma_is_io(buf->vma))
+               while (n_pages)
+                       put_page(pages[--n_pages]);
+
+fail_vma:
+       vb2_put_vma(buf->vma);
+
+fail_pages:
+       kfree(pages); /* kfree is NULL-proof */
+
+fail_buf:
+       kfree(buf);
+
+       return ERR_PTR(ret);
 }
 
-static void vb2_dma_contig_put_userptr(void *mem_priv)
+/*********************************************/
+/*       callbacks for DMABUF buffers        */
+/*********************************************/
+
+static int vb2_dc_map_dmabuf(void *mem_priv)
 {
        struct vb2_dc_buf *buf = mem_priv;
+       struct sg_table *sgt;
+       unsigned long contig_size;
 
-       if (!buf)
+       if (WARN_ON(!buf->db_attach)) {
+               printk(KERN_ERR "trying to pin a non attached buffer\n");
+               return -EINVAL;
+       }
+
+       if (WARN_ON(buf->dma_sgt)) {
+               printk(KERN_ERR "dmabuf buffer is already pinned\n");
+               return 0;
+       }
+
+       /* get the associated scatterlist for this buffer */
+       sgt = dma_buf_map_attachment(buf->db_attach, buf->dma_dir);
+       if (IS_ERR_OR_NULL(sgt)) {
+               printk(KERN_ERR "Error getting dmabuf scatterlist\n");
+               return -EINVAL;
+       }
+
+       /* checking if dmabuf is big enough to store contiguous chunk */
+       contig_size = vb2_dc_get_contiguous_size(sgt);
+       if (contig_size < buf->size) {
+               printk(KERN_ERR "contiguous chunk is too small %lu/%lu b\n",
+                       contig_size, buf->size);
+               dma_buf_unmap_attachment(buf->db_attach, sgt, buf->dma_dir);
+               return -EFAULT;
+       }
+
+       buf->dma_addr = sg_dma_address(sgt->sgl);
+       buf->dma_sgt = sgt;
+
+       return 0;
+}
+
+static void vb2_dc_unmap_dmabuf(void *mem_priv)
+{
+       struct vb2_dc_buf *buf = mem_priv;
+       struct sg_table *sgt = buf->dma_sgt;
+
+       if (WARN_ON(!buf->db_attach)) {
+               printk(KERN_ERR "trying to unpin a not attached buffer\n");
                return;
+       }
 
-       vb2_put_vma(buf->vma);
+       if (WARN_ON(!sgt)) {
+               printk(KERN_ERR "dmabuf buffer is already unpinned\n");
+               return;
+       }
+
+       dma_buf_unmap_attachment(buf->db_attach, sgt, buf->dma_dir);
+
+       buf->dma_addr = 0;
+       buf->dma_sgt = NULL;
+}
+
+static void vb2_dc_detach_dmabuf(void *mem_priv)
+{
+       struct vb2_dc_buf *buf = mem_priv;
+
+       /* if vb2 works correctly you should never detach mapped buffer */
+       if (WARN_ON(buf->dma_addr))
+               vb2_dc_unmap_dmabuf(buf);
+
+       /* detach this attachment */
+       dma_buf_detach(buf->db_attach->dmabuf, buf->db_attach);
        kfree(buf);
 }
 
+static void *vb2_dc_attach_dmabuf(void *alloc_ctx, struct dma_buf *dbuf,
+       unsigned long size, int write)
+{
+       struct vb2_dc_conf *conf = alloc_ctx;
+       struct vb2_dc_buf *buf;
+       struct dma_buf_attachment *dba;
+
+       if (dbuf->size < size)
+               return ERR_PTR(-EFAULT);
+
+       buf = kzalloc(sizeof *buf, GFP_KERNEL);
+       if (!buf)
+               return ERR_PTR(-ENOMEM);
+
+       buf->dev = conf->dev;
+       /* create attachment for the dmabuf with the user device */
+       dba = dma_buf_attach(dbuf, buf->dev);
+       if (IS_ERR(dba)) {
+               printk(KERN_ERR "failed to attach dmabuf\n");
+               kfree(buf);
+               return dba;
+       }
+
+       buf->dma_dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+       buf->size = size;
+       buf->db_attach = dba;
+
+       return buf;
+}
+
+/*********************************************/
+/*       DMA CONTIG exported functions       */
+/*********************************************/
+
 const struct vb2_mem_ops vb2_dma_contig_memops = {
-       .alloc          = vb2_dma_contig_alloc,
-       .put            = vb2_dma_contig_put,
-       .cookie         = vb2_dma_contig_cookie,
-       .vaddr          = vb2_dma_contig_vaddr,
-       .mmap           = vb2_dma_contig_mmap,
-       .get_userptr    = vb2_dma_contig_get_userptr,
-       .put_userptr    = vb2_dma_contig_put_userptr,
-       .num_users      = vb2_dma_contig_num_users,
+       .alloc          = vb2_dc_alloc,
+       .put            = vb2_dc_put,
+       .get_dmabuf     = vb2_dc_get_dmabuf,
+       .cookie         = vb2_dc_cookie,
+       .vaddr          = vb2_dc_vaddr,
+       .mmap           = vb2_dc_mmap,
+       .get_userptr    = vb2_dc_get_userptr,
+       .put_userptr    = vb2_dc_put_userptr,
+       .prepare        = vb2_dc_prepare,
+       .finish         = vb2_dc_finish,
+       .map_dmabuf     = vb2_dc_map_dmabuf,
+       .unmap_dmabuf   = vb2_dc_unmap_dmabuf,
+       .attach_dmabuf  = vb2_dc_attach_dmabuf,
+       .detach_dmabuf  = vb2_dc_detach_dmabuf,
+       .num_users      = vb2_dc_num_users,
 };
 EXPORT_SYMBOL_GPL(vb2_dma_contig_memops);
 
index 504cd4c..81c1ad8 100644 (file)
@@ -136,46 +136,6 @@ int vb2_get_contig_userptr(unsigned long vaddr, unsigned long size,
 }
 EXPORT_SYMBOL_GPL(vb2_get_contig_userptr);
 
-/**
- * vb2_mmap_pfn_range() - map physical pages to userspace
- * @vma:       virtual memory region for the mapping
- * @paddr:     starting physical address of the memory to be mapped
- * @size:      size of the memory to be mapped
- * @vm_ops:    vm operations to be assigned to the created area
- * @priv:      private data to be associated with the area
- *
- * Returns 0 on success.
- */
-int vb2_mmap_pfn_range(struct vm_area_struct *vma, unsigned long paddr,
-                               unsigned long size,
-                               const struct vm_operations_struct *vm_ops,
-                               void *priv)
-{
-       int ret;
-
-       size = min_t(unsigned long, vma->vm_end - vma->vm_start, size);
-
-       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
-       ret = remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT,
-                               size, vma->vm_page_prot);
-       if (ret) {
-               printk(KERN_ERR "Remapping memory failed, error: %d\n", ret);
-               return ret;
-       }
-
-       vma->vm_flags           |= VM_DONTEXPAND | VM_RESERVED;
-       vma->vm_private_data    = priv;
-       vma->vm_ops             = vm_ops;
-
-       vma->vm_ops->open(vma);
-
-       pr_debug("%s: mapped paddr 0x%08lx at 0x%08lx, size %ld\n",
-                       __func__, paddr, vma->vm_start, size);
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(vb2_mmap_pfn_range);
-
 /**
  * vb2_common_vm_open() - increase refcount of the vma
  * @vma:       virtual memory region for the mapping
index 6b5ca6c..305032f 100644 (file)
@@ -29,6 +29,7 @@ struct vb2_vmalloc_buf {
        unsigned int                    n_pages;
        atomic_t                        refcount;
        struct vb2_vmarea_handler       handler;
+       struct dma_buf                  *dbuf;
 };
 
 static void vb2_vmalloc_put(void *buf_priv);
@@ -206,11 +207,66 @@ static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma)
        return 0;
 }
 
+/*********************************************/
+/*       callbacks for DMABUF buffers        */
+/*********************************************/
+
+static int vb2_vmalloc_map_dmabuf(void *mem_priv)
+{
+       struct vb2_vmalloc_buf *buf = mem_priv;
+
+       buf->vaddr = dma_buf_vmap(buf->dbuf);
+
+       return buf->vaddr ? 0 : -EFAULT;
+}
+
+static void vb2_vmalloc_unmap_dmabuf(void *mem_priv)
+{
+       struct vb2_vmalloc_buf *buf = mem_priv;
+
+       dma_buf_vunmap(buf->dbuf, buf->vaddr);
+       buf->vaddr = NULL;
+}
+
+static void vb2_vmalloc_detach_dmabuf(void *mem_priv)
+{
+       struct vb2_vmalloc_buf *buf = mem_priv;
+
+       if (buf->vaddr)
+               dma_buf_vunmap(buf->dbuf, buf->vaddr);
+
+       kfree(buf);
+}
+
+static void *vb2_vmalloc_attach_dmabuf(void *alloc_ctx, struct dma_buf *dbuf,
+       unsigned long size, int write)
+{
+       struct vb2_vmalloc_buf *buf;
+
+       if (dbuf->size < size)
+               return ERR_PTR(-EFAULT);
+
+       buf = kzalloc(sizeof *buf, GFP_KERNEL);
+       if (!buf)
+               return ERR_PTR(-ENOMEM);
+
+       buf->dbuf = dbuf;
+       buf->write = write;
+       buf->size = size;
+
+       return buf;
+}
+
+
 const struct vb2_mem_ops vb2_vmalloc_memops = {
        .alloc          = vb2_vmalloc_alloc,
        .put            = vb2_vmalloc_put,
        .get_userptr    = vb2_vmalloc_get_userptr,
        .put_userptr    = vb2_vmalloc_put_userptr,
+       .map_dmabuf     = vb2_vmalloc_map_dmabuf,
+       .unmap_dmabuf   = vb2_vmalloc_unmap_dmabuf,
+       .attach_dmabuf  = vb2_vmalloc_attach_dmabuf,
+       .detach_dmabuf  = vb2_vmalloc_detach_dmabuf,
        .vaddr          = vb2_vmalloc_vaddr,
        .mmap           = vb2_vmalloc_mmap,
        .num_users      = vb2_vmalloc_num_users,
index 5e8b071..489d685 100644 (file)
@@ -1289,7 +1289,7 @@ static int __init vivi_create_instance(int inst)
        q = &dev->vb_vidq;
        memset(q, 0, sizeof(dev->vb_vidq));
        q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-       q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+       q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
        q->drv_priv = dev;
        q->buf_struct_size = sizeof(struct vivi_buffer);
        q->ops = &vivi_video_qops;
index 85a3ffa..abfb268 100644 (file)
@@ -3,13 +3,15 @@
 
 #ifdef CONFIG_HAVE_GENERIC_DMA_COHERENT
 /*
- * These two functions are only for dma allocator.
+ * These three functions are only for dma allocator.
  * Don't use them in device drivers.
  */
 int dma_alloc_from_coherent(struct device *dev, ssize_t size,
                                       dma_addr_t *dma_handle, void **ret);
 int dma_release_from_coherent(struct device *dev, int order, void *vaddr);
 
+int dma_mmap_from_coherent(struct device *dev, struct vm_area_struct *vma,
+                           void *cpu_addr, size_t size, int *ret);
 /*
  * Standard interface
  */
diff --git a/include/asm-generic/dma-contiguous.h b/include/asm-generic/dma-contiguous.h
new file mode 100644 (file)
index 0000000..c544356
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef ASM_DMA_CONTIGUOUS_H
+#define ASM_DMA_CONTIGUOUS_H
+
+#ifdef __KERNEL__
+#ifdef CONFIG_CMA
+
+#include <linux/device.h>
+#include <linux/dma-contiguous.h>
+
+static inline struct cma *dev_get_cma_area(struct device *dev)
+{
+       if (dev && dev->cma_area)
+               return dev->cma_area;
+       return dma_contiguous_default_area;
+}
+
+static inline void dev_set_cma_area(struct device *dev, struct cma *cma)
+{
+       if (dev)
+               dev->cma_area = cma;
+       if (!dev || !dma_contiguous_default_area)
+               dma_contiguous_default_area = cma;
+}
+
+#endif
+#endif
+
+#endif
index 2e248d8..34841c6 100644 (file)
@@ -176,4 +176,22 @@ dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
 #define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, NULL)
 #define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, NULL)
 
+int
+dma_common_get_sgtable(struct device *dev, struct sg_table *sgt,
+                      void *cpu_addr, dma_addr_t dma_addr, size_t size);
+
+static inline int
+dma_get_sgtable_attrs(struct device *dev, struct sg_table *sgt, void *cpu_addr,
+                     dma_addr_t dma_addr, size_t size, struct dma_attrs *attrs)
+{
+       struct dma_map_ops *ops = get_dma_ops(dev);
+       BUG_ON(!ops);
+       if (ops->get_sgtable)
+               return ops->get_sgtable(dev, sgt, cpu_addr, dma_addr, size,
+                                       attrs);
+       return dma_common_get_sgtable(dev, sgt, cpu_addr, dma_addr, size);
+}
+
+#define dma_get_sgtable(d, t, v, h, s) dma_get_sgtable_attrs(d, t, v, h, s, NULL)
+
 #endif
index 5ad17cc..e339929 100644 (file)
@@ -661,6 +661,10 @@ struct device {
 
        struct dma_coherent_mem *dma_mem; /* internal for coherent mem
                                             override */
+#ifdef CONFIG_CMA
+       struct cma *cma_area;           /* contiguous memory area for dma
+                                          allocations */
+#endif
        /* arch specific additions */
        struct dev_archdata     archdata;
 
index 547ab56..f83f793 100644 (file)
@@ -15,6 +15,8 @@ enum dma_attr {
        DMA_ATTR_WEAK_ORDERING,
        DMA_ATTR_WRITE_COMBINE,
        DMA_ATTR_NON_CONSISTENT,
+       DMA_ATTR_NO_KERNEL_MAPPING,
+       DMA_ATTR_SKIP_CPU_SYNC,
        DMA_ATTR_MAX,
 };
 
index 3efbfc2..eb48f38 100644 (file)
@@ -61,6 +61,13 @@ struct dma_buf_attachment;
  *                This Callback must not sleep.
  * @kmap: maps a page from the buffer into kernel address space.
  * @kunmap: [optional] unmaps a page from the buffer.
+ * @mmap: used to expose the backing storage to userspace. Note that the
+ *       mapping needs to be coherent - if the exporter doesn't directly
+ *       support this, it needs to fake coherency by shooting down any ptes
+ *       when transitioning away from the cpu domain.
+ * @vmap: [optional] creates a virtual mapping for the buffer into kernel
+ *       address space. Same restrictions as for vmap and friends apply.
+ * @vunmap: [optional] unmaps a vmap from the buffer
  */
 struct dma_buf_ops {
        int (*attach)(struct dma_buf *, struct device *,
@@ -92,6 +99,11 @@ struct dma_buf_ops {
        void (*kunmap_atomic)(struct dma_buf *, unsigned long, void *);
        void *(*kmap)(struct dma_buf *, unsigned long);
        void (*kunmap)(struct dma_buf *, unsigned long, void *);
+
+       int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
+
+       void *(*vmap)(struct dma_buf *);
+       void (*vunmap)(struct dma_buf *, void *vaddr);
 };
 
 /**
@@ -167,6 +179,11 @@ void *dma_buf_kmap_atomic(struct dma_buf *, unsigned long);
 void dma_buf_kunmap_atomic(struct dma_buf *, unsigned long, void *);
 void *dma_buf_kmap(struct dma_buf *, unsigned long);
 void dma_buf_kunmap(struct dma_buf *, unsigned long, void *);
+
+int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *,
+                unsigned long);
+void *dma_buf_vmap(struct dma_buf *);
+void dma_buf_vunmap(struct dma_buf *, void *vaddr);
 #else
 
 static inline struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf,
@@ -248,6 +265,22 @@ static inline void dma_buf_kunmap(struct dma_buf *dmabuf,
                                  unsigned long pnum, void *vaddr)
 {
 }
+
+static inline int dma_buf_mmap(struct dma_buf *dmabuf,
+                              struct vm_area_struct *vma,
+                              unsigned long pgoff)
+{
+       return -ENODEV;
+}
+
+static inline void *dma_buf_vmap(struct dma_buf *dmabuf)
+{
+       return NULL;
+}
+
+static inline void dma_buf_vunmap(struct dma_buf *dmabuf, void *vaddr)
+{
+}
 #endif /* CONFIG_DMA_SHARED_BUFFER */
 
 #endif /* __DMA_BUF_H__ */
diff --git a/include/linux/dma-contiguous.h b/include/linux/dma-contiguous.h
new file mode 100644 (file)
index 0000000..2f303e4
--- /dev/null
@@ -0,0 +1,110 @@
+#ifndef __LINUX_CMA_H
+#define __LINUX_CMA_H
+
+/*
+ * Contiguous Memory Allocator for DMA mapping framework
+ * Copyright (c) 2010-2011 by Samsung Electronics.
+ * Written by:
+ *     Marek Szyprowski <m.szyprowski@samsung.com>
+ *     Michal Nazarewicz <mina86@mina86.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License or (at your optional) any later version of the license.
+ */
+
+/*
+ * Contiguous Memory Allocator
+ *
+ *   The Contiguous Memory Allocator (CMA) makes it possible to
+ *   allocate big contiguous chunks of memory after the system has
+ *   booted.
+ *
+ * Why is it needed?
+ *
+ *   Various devices on embedded systems have no scatter-getter and/or
+ *   IO map support and require contiguous blocks of memory to
+ *   operate.  They include devices such as cameras, hardware video
+ *   coders, etc.
+ *
+ *   Such devices often require big memory buffers (a full HD frame
+ *   is, for instance, more then 2 mega pixels large, i.e. more than 6
+ *   MB of memory), which makes mechanisms such as kmalloc() or
+ *   alloc_page() ineffective.
+ *
+ *   At the same time, a solution where a big memory region is
+ *   reserved for a device is suboptimal since often more memory is
+ *   reserved then strictly required and, moreover, the memory is
+ *   inaccessible to page system even if device drivers don't use it.
+ *
+ *   CMA tries to solve this issue by operating on memory regions
+ *   where only movable pages can be allocated from.  This way, kernel
+ *   can use the memory for pagecache and when device driver requests
+ *   it, allocated pages can be migrated.
+ *
+ * Driver usage
+ *
+ *   CMA should not be used by the device drivers directly. It is
+ *   only a helper framework for dma-mapping subsystem.
+ *
+ *   For more information, see kernel-docs in drivers/base/dma-contiguous.c
+ */
+
+#ifdef __KERNEL__
+
+struct cma;
+struct page;
+struct device;
+
+#ifdef CONFIG_CMA
+
+/*
+ * There is always at least global CMA area and a few optional device
+ * private areas configured in kernel .config.
+ */
+#define MAX_CMA_AREAS  (1 + CONFIG_CMA_AREAS)
+
+extern struct cma *dma_contiguous_default_area;
+
+void dma_contiguous_reserve(phys_addr_t addr_limit);
+int dma_declare_contiguous(struct device *dev, unsigned long size,
+                          phys_addr_t base, phys_addr_t limit);
+
+struct page *dma_alloc_from_contiguous(struct device *dev, int count,
+                                      unsigned int order);
+bool dma_release_from_contiguous(struct device *dev, struct page *pages,
+                                int count);
+
+#else
+
+#define MAX_CMA_AREAS  (0)
+
+static inline void dma_contiguous_reserve(phys_addr_t limit) { }
+
+static inline
+int dma_declare_contiguous(struct device *dev, unsigned long size,
+                          phys_addr_t base, phys_addr_t limit)
+{
+       return -ENOSYS;
+}
+
+static inline
+struct page *dma_alloc_from_contiguous(struct device *dev, int count,
+                                      unsigned int order)
+{
+       return NULL;
+}
+
+static inline
+bool dma_release_from_contiguous(struct device *dev, struct page *pages,
+                                int count)
+{
+       return false;
+}
+
+#endif
+
+#endif
+
+#endif
index dfc099e..94af418 100644 (file)
@@ -18,6 +18,9 @@ struct dma_map_ops {
        int (*mmap)(struct device *, struct vm_area_struct *,
                          void *, dma_addr_t, size_t, struct dma_attrs *attrs);
 
+       int (*get_sgtable)(struct device *dev, struct sg_table *sgt, void *,
+                          dma_addr_t, size_t, struct dma_attrs *attrs);
+
        dma_addr_t (*map_page)(struct device *dev, struct page *page,
                               unsigned long offset, size_t size,
                               enum dma_data_direction dir,
index 581e74b..1e49be4 100644 (file)
@@ -391,4 +391,16 @@ static inline bool pm_suspended_storage(void)
 }
 #endif /* CONFIG_PM_SLEEP */
 
+#ifdef CONFIG_CMA
+
+/* The below functions must be run on a range from a single zone. */
+extern int alloc_contig_range(unsigned long start, unsigned long end,
+                             unsigned migratetype);
+extern void free_contig_range(unsigned long pfn, unsigned nr_pages);
+
+/* CMA stuff */
+extern void init_cma_reserved_pageblock(struct page *page);
+
+#endif
+
 #endif /* __LINUX_GFP_H */
index dff7115..26f2040 100644 (file)
  */
 #define PAGE_ALLOC_COSTLY_ORDER 3
 
-#define MIGRATE_UNMOVABLE     0
-#define MIGRATE_RECLAIMABLE   1
-#define MIGRATE_MOVABLE       2
-#define MIGRATE_PCPTYPES      3 /* the number of types on the pcp lists */
-#define MIGRATE_RESERVE       3
-#define MIGRATE_ISOLATE       4 /* can't allocate from here */
-#define MIGRATE_TYPES         5
+enum {
+       MIGRATE_UNMOVABLE,
+       MIGRATE_RECLAIMABLE,
+       MIGRATE_MOVABLE,
+       MIGRATE_PCPTYPES,       /* the number of types on the pcp lists */
+       MIGRATE_RESERVE = MIGRATE_PCPTYPES,
+#ifdef CONFIG_CMA
+       /*
+        * MIGRATE_CMA migration type is designed to mimic the way
+        * ZONE_MOVABLE works.  Only movable pages can be allocated
+        * from MIGRATE_CMA pageblocks and page allocator never
+        * implicitly change migration type of MIGRATE_CMA pageblock.
+        *
+        * The way to use it is to change migratetype of a range of
+        * pageblocks to MIGRATE_CMA which can be done by
+        * __free_pageblock_cma() function.  What is important though
+        * is that a range of pageblocks must be aligned to
+        * MAX_ORDER_NR_PAGES should biggest page be bigger then
+        * a single pageblock.
+        */
+       MIGRATE_CMA,
+#endif
+       MIGRATE_ISOLATE,        /* can't allocate from here */
+       MIGRATE_TYPES
+};
+
+#ifdef CONFIG_CMA
+#  define is_migrate_cma(migratetype) unlikely((migratetype) == MIGRATE_CMA)
+#  define cma_wmark_pages(zone)        zone->min_cma_pages
+#else
+#  define is_migrate_cma(migratetype) false
+#  define cma_wmark_pages(zone) 0
+#endif
 
 #define for_each_migratetype_order(order, type) \
        for (order = 0; order < MAX_ORDER; order++) \
@@ -346,6 +372,13 @@ struct zone {
 #ifdef CONFIG_MEMORY_HOTPLUG
        /* see spanned/present_pages for more description */
        seqlock_t               span_seqlock;
+#endif
+#ifdef CONFIG_CMA
+       /*
+        * CMA needs to increase watermark levels during the allocation
+        * process to make sure that the system is not starved.
+        */
+       unsigned long           min_cma_pages;
 #endif
        struct free_area        free_area[MAX_ORDER];
 
index 051c1b1..3bdcab3 100644 (file)
@@ -3,7 +3,7 @@
 
 /*
  * Changes migrate type in [start_pfn, end_pfn) to be MIGRATE_ISOLATE.
- * If specified range includes migrate types other than MOVABLE,
+ * If specified range includes migrate types other than MOVABLE or CMA,
  * this will fail with -EBUSY.
  *
  * For isolating all pages in the range finally, the caller have to
  * test it.
  */
 extern int
-start_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn);
+start_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn,
+                        unsigned migratetype);
 
 /*
  * Changes MIGRATE_ISOLATE to MIGRATE_MOVABLE.
  * target range is [start_pfn, end_pfn)
  */
 extern int
-undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn);
+undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn,
+                       unsigned migratetype);
 
 /*
- * test all pages in [start_pfn, end_pfn)are isolated or not.
+ * Test all pages in [start_pfn, end_pfn) are isolated or not.
  */
-extern int
-test_pages_isolated(unsigned long start_pfn, unsigned long end_pfn);
+int test_pages_isolated(unsigned long start_pfn, unsigned long end_pfn);
 
 /*
- * Internal funcs.Changes pageblock's migrate type.
- * Please use make_pagetype_isolated()/make_pagetype_movable().
+ * Internal functions. Changes pageblock's migrate type.
  */
 extern int set_migratetype_isolate(struct page *page);
-extern void unset_migratetype_isolate(struct page *page);
+extern void unset_migratetype_isolate(struct page *page, unsigned migratetype);
 
 
 #endif
index ac9586d..7b600da 100644 (file)
@@ -214,6 +214,10 @@ void sg_free_table(struct sg_table *);
 int __sg_alloc_table(struct sg_table *, unsigned int, unsigned int, gfp_t,
                     sg_alloc_fn *);
 int sg_alloc_table(struct sg_table *, unsigned int, gfp_t);
+int sg_alloc_table_from_pages(struct sg_table *sgt,
+       struct page **pages, unsigned int n_pages,
+       unsigned long offset, unsigned long size,
+       gfp_t gfp_mask);
 
 size_t sg_copy_from_buffer(struct scatterlist *sgl, unsigned int nents,
                           void *buf, size_t buflen);
index c9c9a46..52f2aa0 100644 (file)
@@ -185,6 +185,7 @@ enum v4l2_memory {
        V4L2_MEMORY_MMAP             = 1,
        V4L2_MEMORY_USERPTR          = 2,
        V4L2_MEMORY_OVERLAY          = 3,
+       V4L2_MEMORY_DMABUF           = 4,
 };
 
 /* see also http://vektor.theorem.ca/graphics/ycbcr/ */
@@ -588,6 +589,8 @@ struct v4l2_requestbuffers {
  *                     should be passed to mmap() called on the video node)
  * @userptr:           when memory is V4L2_MEMORY_USERPTR, a userspace pointer
  *                     pointing to this plane
+ * @fd:                        when memory is V4L2_MEMORY_DMABUF, a userspace file
+ *                     descriptor associated with this plane
  * @data_offset:       offset in the plane to the start of data; usually 0,
  *                     unless there is a header in front of the data
  *
@@ -602,6 +605,7 @@ struct v4l2_plane {
        union {
                __u32           mem_offset;
                unsigned long   userptr;
+               int             fd;
        } m;
        __u32                   data_offset;
        __u32                   reserved[11];
@@ -624,6 +628,8 @@ struct v4l2_plane {
  *             (or a "cookie" that should be passed to mmap() as offset)
  * @userptr:   for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR;
  *             a userspace pointer pointing to this buffer
+ * @fd:                for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF;
+ *             a userspace file descriptor associated with this buffer
  * @planes:    for multiplanar buffers; userspace pointer to the array of plane
  *             info structs for this buffer
  * @length:    size in bytes of the buffer (NOT its payload) for single-plane
@@ -650,6 +656,7 @@ struct v4l2_buffer {
                __u32           offset;
                unsigned long   userptr;
                struct v4l2_plane *planes;
+               int             fd;
        } m;
        __u32                   length;
        __u32                   input;
@@ -672,6 +679,31 @@ struct v4l2_buffer {
 #define V4L2_BUF_FLAG_NO_CACHE_INVALIDATE      0x0800
 #define V4L2_BUF_FLAG_NO_CACHE_CLEAN           0x1000
 
+/**
+ * struct v4l2_exportbuffer - export of video buffer as DMABUF file descriptor
+ *
+ * @fd:                file descriptor associated with DMABUF (set by driver)
+ * @mem_offset:        buffer memory offset as returned by VIDIOC_QUERYBUF in struct
+ *             v4l2_buffer::m.offset (for single-plane formats) or
+ *             v4l2_plane::m.offset (for multi-planar formats)
+ * @flags:     flags for newly created file, currently only O_CLOEXEC is
+ *             supported, refer to manual of open syscall for more details
+ *
+ * Contains data used for exporting a video buffer as DMABUF file descriptor.
+ * The buffer is identified by a 'cookie' returned by VIDIOC_QUERYBUF
+ * (identical to the cookie used to mmap() the buffer to userspace). All
+ * reserved fields must be set to zero. The field reserved0 is expected to
+ * become a structure 'type' allowing an alternative layout of the structure
+ * content. Therefore this field should not be used for any other extensions.
+ */
+struct v4l2_exportbuffer {
+       __u32           fd;
+       __u32           reserved0;
+       __u32           mem_offset;
+       __u32           flags;
+       __u32           reserved[12];
+};
+
 /*
  *     O V E R L A Y   P R E V I E W
  */
@@ -2319,6 +2351,7 @@ struct v4l2_create_buffers {
 #define VIDIOC_S_FBUF           _IOW('V', 11, struct v4l2_framebuffer)
 #define VIDIOC_OVERLAY          _IOW('V', 14, int)
 #define VIDIOC_QBUF            _IOWR('V', 15, struct v4l2_buffer)
+#define VIDIOC_EXPBUF          _IOWR('V', 16, struct v4l2_exportbuffer)
 #define VIDIOC_DQBUF           _IOWR('V', 17, struct v4l2_buffer)
 #define VIDIOC_STREAMON                 _IOW('V', 18, int)
 #define VIDIOC_STREAMOFF        _IOW('V', 19, int)
index dcdfc2b..8a9555a 100644 (file)
@@ -14,6 +14,7 @@ struct vm_area_struct;                /* vma defining user mapping in mm_types.h */
 #define VM_USERMAP     0x00000008      /* suitable for remap_vmalloc_range */
 #define VM_VPAGES      0x00000010      /* buffer for pages was vmalloc'ed */
 #define VM_UNLIST      0x00000020      /* vm_struct is not listed in vmlist */
+#define VM_DMA         0x00000040      /* used by dma-mapping framework */
 /* bits [20..32] reserved for arch specific ioremap internals */
 
 /*
@@ -32,7 +33,7 @@ struct vm_struct {
        struct page             **pages;
        unsigned int            nr_pages;
        phys_addr_t             phys_addr;
-       void                    *caller;
+       const void              *caller;
 };
 
 /*
@@ -62,7 +63,7 @@ extern void *vmalloc_32_user(unsigned long size);
 extern void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot);
 extern void *__vmalloc_node_range(unsigned long size, unsigned long align,
                        unsigned long start, unsigned long end, gfp_t gfp_mask,
-                       pgprot_t prot, int node, void *caller);
+                       pgprot_t prot, int node, const void *caller);
 extern void vfree(const void *addr);
 
 extern void *vmap(struct page **pages, unsigned int count,
@@ -85,14 +86,15 @@ static inline size_t get_vm_area_size(const struct vm_struct *area)
 
 extern struct vm_struct *get_vm_area(unsigned long size, unsigned long flags);
 extern struct vm_struct *get_vm_area_caller(unsigned long size,
-                                       unsigned long flags, void *caller);
+                                       unsigned long flags, const void *caller);
 extern struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,
                                        unsigned long start, unsigned long end);
 extern struct vm_struct *__get_vm_area_caller(unsigned long size,
                                        unsigned long flags,
                                        unsigned long start, unsigned long end,
-                                       void *caller);
+                                       const void *caller);
 extern struct vm_struct *remove_vm_area(const void *addr);
+extern struct vm_struct *find_vm_area(const void *addr);
 
 extern int map_vm_area(struct vm_struct *area, pgprot_t prot,
                        struct page ***pages);
index 96d2221..a5ecec6 100644 (file)
@@ -126,8 +126,10 @@ struct video_device
 
        /* ioctl callbacks */
        const struct v4l2_ioctl_ops *ioctl_ops;
+       DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
 
        /* serialization lock */
+       DECLARE_BITMAP(dont_use_lock, BASE_VIDIOC_PRIVATE);
        struct mutex *lock;
 };
 
@@ -173,6 +175,26 @@ void video_device_release(struct video_device *vdev);
    a dubious construction at best. */
 void video_device_release_empty(struct video_device *vdev);
 
+/* returns true if cmd is a known V4L2 ioctl */
+bool v4l2_is_known_ioctl(unsigned int cmd);
+
+/* mark that this command shouldn't use core locking */
+static inline void v4l2_dont_use_lock(struct video_device *vdev, unsigned int cmd)
+{
+       if (_IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
+               set_bit(_IOC_NR(cmd), vdev->dont_use_lock);
+}
+
+/* Mark that this command isn't implemented, must be called before
+   video_device_register. See also the comments in determine_valid_ioctls().
+   This function allows drivers to provide just one v4l2_ioctl_ops struct, but
+   disable ioctls based on the specific card that is actually found. */
+static inline void v4l2_dont_use_cmd(struct video_device *vdev, unsigned int cmd)
+{
+       if (_IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
+               set_bit(_IOC_NR(cmd), vdev->valid_ioctls);
+}
+
 /* helper functions to access driver private data. */
 static inline void *video_get_drvdata(struct video_device *vdev)
 {
index 3cb939c..c161ec3 100644 (file)
@@ -119,6 +119,8 @@ struct v4l2_ioctl_ops {
        int (*vidioc_reqbufs) (struct file *file, void *fh, struct v4l2_requestbuffers *b);
        int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *b);
        int (*vidioc_qbuf)    (struct file *file, void *fh, struct v4l2_buffer *b);
+       int (*vidioc_expbuf)  (struct file *file, void *fh,
+                               struct v4l2_exportbuffer *e);
        int (*vidioc_dqbuf)   (struct file *file, void *fh, struct v4l2_buffer *b);
 
        int (*vidioc_create_bufs)(struct file *file, void *fh, struct v4l2_create_buffers *b);
index 16ac473..2bef410 100644 (file)
@@ -106,6 +106,9 @@ int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
 int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
                      struct v4l2_buffer *buf);
 
+int v4l2_m2m_expbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+                       struct v4l2_exportbuffer *eb);
+
 int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
                  struct v4l2_buffer *buf);
 int v4l2_m2m_dqbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
index a15d1f1..fe01f95 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/mutex.h>
 #include <linux/poll.h>
 #include <linux/videodev2.h>
+#include <linux/dma-buf.h>
 
 struct vb2_alloc_ctx;
 struct vb2_fileio_data;
@@ -41,6 +42,24 @@ struct vb2_fileio_data;
  *              argument to other ops in this structure
  * @put_userptr: inform the allocator that a USERPTR buffer will no longer
  *              be used
+ * @attach_dmabuf: attach a shared struct dma_buf for a hardware operation;
+ *                used for DMABUF memory types; alloc_ctx is the alloc context
+ *                dbuf is the shared dma_buf; returns NULL on failure;
+ *                allocator private per-buffer structure on success;
+ *                this needs to be used for further accesses to the buffer
+ * @detach_dmabuf: inform the exporter of the buffer that the current DMABUF
+ *                buffer is no longer used; the buf_priv argument is the
+ *                allocator private per-buffer structure previously returned
+ *                from the attach_dmabuf callback
+ * @map_dmabuf: request for access to the dmabuf from allocator; the allocator
+ *             of dmabuf is informed that this driver is going to use the
+ *             dmabuf
+ * @unmap_dmabuf: releases access control to the dmabuf - allocator is notified
+ *               that this driver is done using the dmabuf for now
+ * @prepare:   called everytime the buffer is passed from userspace to the
+ *             driver, usefull for cache synchronisation, optional
+ * @finish:    called everytime the buffer is passed back from the driver
+ *             to the userspace, also optional
  * @vaddr:     return a kernel virtual address to a given memory buffer
  *             associated with the passed private structure or NULL if no
  *             such mapping exists
@@ -56,15 +75,27 @@ struct vb2_fileio_data;
  * Required ops for USERPTR types: get_userptr, put_userptr.
  * Required ops for MMAP types: alloc, put, num_users, mmap.
  * Required ops for read/write access types: alloc, put, num_users, vaddr
+ * Required ops for DMABUF types: attach_dmabuf, detach_dmabuf, map_dmabuf,
+ *                               unmap_dmabuf.
  */
 struct vb2_mem_ops {
        void            *(*alloc)(void *alloc_ctx, unsigned long size);
        void            (*put)(void *buf_priv);
+       struct dma_buf *(*get_dmabuf)(void *buf_priv);
 
        void            *(*get_userptr)(void *alloc_ctx, unsigned long vaddr,
                                        unsigned long size, int write);
        void            (*put_userptr)(void *buf_priv);
 
+       void            (*prepare)(void *buf_priv);
+       void            (*finish)(void *buf_priv);
+
+       void            *(*attach_dmabuf)(void *alloc_ctx, struct dma_buf *dbuf,
+                               unsigned long size, int write);
+       void            (*detach_dmabuf)(void *buf_priv);
+       int             (*map_dmabuf)(void *buf_priv);
+       void            (*unmap_dmabuf)(void *buf_priv);
+
        void            *(*vaddr)(void *buf_priv);
        void            *(*cookie)(void *buf_priv);
 
@@ -75,6 +106,8 @@ struct vb2_mem_ops {
 
 struct vb2_plane {
        void                    *mem_priv;
+       struct dma_buf          *dbuf;
+       unsigned int            dbuf_mapped;
 };
 
 /**
@@ -83,12 +116,14 @@ struct vb2_plane {
  * @VB2_USERPTR:       driver supports USERPTR with streaming API
  * @VB2_READ:          driver supports read() style access
  * @VB2_WRITE:         driver supports write() style access
+ * @VB2_DMABUF:                driver supports DMABUF with streaming API
  */
 enum vb2_io_modes {
        VB2_MMAP        = (1 << 0),
        VB2_USERPTR     = (1 << 1),
        VB2_READ        = (1 << 2),
        VB2_WRITE       = (1 << 3),
+       VB2_DMABUF      = (1 << 4),
 };
 
 /**
@@ -316,6 +351,7 @@ int vb2_queue_init(struct vb2_queue *q);
 void vb2_queue_release(struct vb2_queue *q);
 
 int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
+int vb2_expbuf(struct vb2_queue *q, struct v4l2_exportbuffer *eb);
 int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking);
 
 int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
index 84e1f6c..f05444c 100644 (file)
@@ -33,11 +33,6 @@ extern const struct vm_operations_struct vb2_common_vm_ops;
 int vb2_get_contig_userptr(unsigned long vaddr, unsigned long size,
                           struct vm_area_struct **res_vma, dma_addr_t *res_pa);
 
-int vb2_mmap_pfn_range(struct vm_area_struct *vma, unsigned long paddr,
-                               unsigned long size,
-                               const struct vm_operations_struct *vm_ops,
-                               void *priv);
-
 struct vm_area_struct *vb2_get_vma(struct vm_area_struct *vma);
 void vb2_put_vma(struct vm_area_struct *vma);
 
index 6096e89..9a3d483 100644 (file)
@@ -318,6 +318,69 @@ int sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask)
 }
 EXPORT_SYMBOL(sg_alloc_table);
 
+/**
+ * sg_alloc_table_from_pages - Allocate and initialize an sg table from
+ *                            an array of pages
+ * @sgt:       The sg table header to use
+ * @pages:     Pointer to an array of page pointers
+ * @n_pages:   Number of pages in the pages array
+ * @offset:     Offset from a start of the first page to a start of a buffer
+ * @size:       Number of valid bytes in the buffer (after offset)
+ * @gfp_mask:  GFP allocation mask
+ *
+ *  Description:
+ *    Allocate and initialize an sg table from a list of pages. Continuous
+ *    ranges of the pages are squashed into a single scatterlist node. A user
+ *    may provide an offset at a start and a size of valid data in a buffer
+ *    specified by the page array. The returned sg table is released by
+ *    sg_free_table.
+ *
+ * Returns:
+ *   0 on success, negative error on failure
+ **/
+int sg_alloc_table_from_pages(struct sg_table *sgt,
+       struct page **pages, unsigned int n_pages,
+       unsigned long offset, unsigned long size,
+       gfp_t gfp_mask)
+{
+       unsigned int chunks;
+       unsigned int i;
+       unsigned int cur_page;
+       int ret;
+       struct scatterlist *s;
+
+       /* compute number of contiguous chunks */
+       chunks = 1;
+       for (i = 1; i < n_pages; ++i)
+               if (pages[i] != pages[i - 1] + 1)
+                       ++chunks;
+
+       ret = sg_alloc_table(sgt, chunks, gfp_mask);
+       if (unlikely(ret))
+               return ret;
+
+       /* merging chunks and putting them into the scatterlist */
+       cur_page = 0;
+       for_each_sg(sgt->sgl, s, sgt->orig_nents, i) {
+               unsigned long chunk_size;
+               unsigned int j;
+
+               /* looking for the end of the current chunk */
+               for (j = cur_page + 1; j < n_pages; ++j)
+                       if (pages[j] != pages[j - 1] + 1)
+                               break;
+
+               chunk_size = ((j - cur_page) << PAGE_SHIFT) - offset;
+               sg_set_page(s, pages[cur_page], min(size, chunk_size), offset);
+               size -= chunk_size;
+               offset = 0;
+               cur_page = j;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(sg_alloc_table_from_pages);
+
 /**
  * sg_miter_start - start mapping iteration over a sg list
  * @miter: sg mapping iter to be started
index e338407..3922002 100644 (file)
@@ -198,7 +198,7 @@ config COMPACTION
 config MIGRATION
        bool "Page migration"
        def_bool y
-       depends on NUMA || ARCH_ENABLE_MEMORY_HOTREMOVE || COMPACTION
+       depends on NUMA || ARCH_ENABLE_MEMORY_HOTREMOVE || COMPACTION || CMA
        help
          Allows the migration of the physical location of pages of processes
          while the virtual addresses are not changed. This is useful in
index 50ec00e..8aada89 100644 (file)
@@ -13,7 +13,7 @@ obj-y                 := filemap.o mempool.o oom_kill.o fadvise.o \
                           readahead.o swap.o truncate.o vmscan.o shmem.o \
                           prio_tree.o util.o mmzone.o vmstat.o backing-dev.o \
                           page_isolation.o mm_init.o mmu_context.o percpu.o \
-                          $(mmu-y)
+                          compaction.o $(mmu-y)
 obj-y += init-mm.o
 
 ifdef CONFIG_NO_BOOTMEM
@@ -32,7 +32,6 @@ obj-$(CONFIG_NUMA)    += mempolicy.o
 obj-$(CONFIG_SPARSEMEM)        += sparse.o
 obj-$(CONFIG_SPARSEMEM_VMEMMAP) += sparse-vmemmap.o
 obj-$(CONFIG_SLOB) += slob.o
-obj-$(CONFIG_COMPACTION) += compaction.o
 obj-$(CONFIG_MMU_NOTIFIER) += mmu_notifier.o
 obj-$(CONFIG_KSM) += ksm.o
 obj-$(CONFIG_PAGE_POISONING) += debug-pagealloc.o
index 74a8c82..da7d35e 100644 (file)
 #include <linux/sysfs.h>
 #include "internal.h"
 
+#if defined CONFIG_COMPACTION || defined CONFIG_CMA
+
 #define CREATE_TRACE_POINTS
 #include <trace/events/compaction.h>
 
-/*
- * compact_control is used to track pages being migrated and the free pages
- * they are being migrated to during memory compaction. The free_pfn starts
- * at the end of a zone and migrate_pfn begins at the start. Movable pages
- * are moved to the end of a zone during a compaction run and the run
- * completes when free_pfn <= migrate_pfn
- */
-struct compact_control {
-       struct list_head freepages;     /* List of free pages to migrate to */
-       struct list_head migratepages;  /* List of pages being migrated */
-       unsigned long nr_freepages;     /* Number of isolated free pages */
-       unsigned long nr_migratepages;  /* Number of pages to migrate */
-       unsigned long free_pfn;         /* isolate_freepages search base */
-       unsigned long migrate_pfn;      /* isolate_migratepages search base */
-       bool sync;                      /* Synchronous migration */
-
-       int order;                      /* order a direct compactor needs */
-       int migratetype;                /* MOVABLE, RECLAIMABLE etc */
-       struct zone *zone;
-};
-
 static unsigned long release_freepages(struct list_head *freelist)
 {
        struct page *page, *next;
@@ -54,24 +35,35 @@ static unsigned long release_freepages(struct list_head *freelist)
        return count;
 }
 
-/* Isolate free pages onto a private freelist. Must hold zone->lock */
-static unsigned long isolate_freepages_block(struct zone *zone,
-                               unsigned long blockpfn,
-                               struct list_head *freelist)
+static void map_pages(struct list_head *list)
+{
+       struct page *page;
+
+       list_for_each_entry(page, list, lru) {
+               arch_alloc_page(page, 0);
+               kernel_map_pages(page, 1, 1);
+       }
+}
+
+static inline bool migrate_async_suitable(int migratetype)
+{
+       return is_migrate_cma(migratetype) || migratetype == MIGRATE_MOVABLE;
+}
+
+/*
+ * Isolate free pages onto a private freelist. Caller must hold zone->lock.
+ * If @strict is true, will abort returning 0 on any invalid PFNs or non-free
+ * pages inside of the pageblock (even though it may still end up isolating
+ * some pages).
+ */
+static unsigned long isolate_freepages_block(unsigned long blockpfn,
+                               unsigned long end_pfn,
+                               struct list_head *freelist,
+                               bool strict)
 {
-       unsigned long zone_end_pfn, end_pfn;
        int nr_scanned = 0, total_isolated = 0;
        struct page *cursor;
 
-       /* Get the last PFN we should scan for free pages at */
-       zone_end_pfn = zone->zone_start_pfn + zone->spanned_pages;
-       end_pfn = min(blockpfn + pageblock_nr_pages, zone_end_pfn);
-
-       /* Find the first usable PFN in the block to initialse page cursor */
-       for (; blockpfn < end_pfn; blockpfn++) {
-               if (pfn_valid_within(blockpfn))
-                       break;
-       }
        cursor = pfn_to_page(blockpfn);
 
        /* Isolate free pages. This assumes the block is valid */
@@ -79,15 +71,23 @@ static unsigned long isolate_freepages_block(struct zone *zone,
                int isolated, i;
                struct page *page = cursor;
 
-               if (!pfn_valid_within(blockpfn))
+               if (!pfn_valid_within(blockpfn)) {
+                       if (strict)
+                               return 0;
                        continue;
+               }
                nr_scanned++;
 
-               if (!PageBuddy(page))
+               if (!PageBuddy(page)) {
+                       if (strict)
+                               return 0;
                        continue;
+               }
 
                /* Found a free page, break it into order-0 pages */
                isolated = split_free_page(page);
+               if (!isolated && strict)
+                       return 0;
                total_isolated += isolated;
                for (i = 0; i < isolated; i++) {
                        list_add(&page->lru, freelist);
@@ -105,114 +105,71 @@ static unsigned long isolate_freepages_block(struct zone *zone,
        return total_isolated;
 }
 
-/* Returns true if the page is within a block suitable for migration to */
-static bool suitable_migration_target(struct page *page)
-{
-
-       int migratetype = get_pageblock_migratetype(page);
-
-       /* Don't interfere with memory hot-remove or the min_free_kbytes blocks */
-       if (migratetype == MIGRATE_ISOLATE || migratetype == MIGRATE_RESERVE)
-               return false;
-
-       /* If the page is a large free page, then allow migration */
-       if (PageBuddy(page) && page_order(page) >= pageblock_order)
-               return true;
-
-       /* If the block is MIGRATE_MOVABLE, allow migration */
-       if (migratetype == MIGRATE_MOVABLE)
-               return true;
-
-       /* Otherwise skip the block */
-       return false;
-}
-
-/*
- * Based on information in the current compact_control, find blocks
- * suitable for isolating free pages from and then isolate them.
+/**
+ * isolate_freepages_range() - isolate free pages.
+ * @start_pfn: The first PFN to start isolating.
+ * @end_pfn:   The one-past-last PFN.
+ *
+ * Non-free pages, invalid PFNs, or zone boundaries within the
+ * [start_pfn, end_pfn) range are considered errors, cause function to
+ * undo its actions and return zero.
+ *
+ * Otherwise, function returns one-past-the-last PFN of isolated page
+ * (which may be greater then end_pfn if end fell in a middle of
+ * a free page).
  */
-static void isolate_freepages(struct zone *zone,
-                               struct compact_control *cc)
+unsigned long
+isolate_freepages_range(unsigned long start_pfn, unsigned long end_pfn)
 {
-       struct page *page;
-       unsigned long high_pfn, low_pfn, pfn;
-       unsigned long flags;
-       int nr_freepages = cc->nr_freepages;
-       struct list_head *freelist = &cc->freepages;
-
-       /*
-        * Initialise the free scanner. The starting point is where we last
-        * scanned from (or the end of the zone if starting). The low point
-        * is the end of the pageblock the migration scanner is using.
-        */
-       pfn = cc->free_pfn;
-       low_pfn = cc->migrate_pfn + pageblock_nr_pages;
+       unsigned long isolated, pfn, block_end_pfn, flags;
+       struct zone *zone = NULL;
+       LIST_HEAD(freelist);
 
-       /*
-        * Take care that if the migration scanner is at the end of the zone
-        * that the free scanner does not accidentally move to the next zone
-        * in the next isolation cycle.
-        */
-       high_pfn = min(low_pfn, pfn);
-
-       /*
-        * Isolate free pages until enough are available to migrate the
-        * pages on cc->migratepages. We stop searching if the migrate
-        * and free page scanners meet or enough free pages are isolated.
-        */
-       for (; pfn > low_pfn && cc->nr_migratepages > nr_freepages;
-                                       pfn -= pageblock_nr_pages) {
-               unsigned long isolated;
+       if (pfn_valid(start_pfn))
+               zone = page_zone(pfn_to_page(start_pfn));
 
-               if (!pfn_valid(pfn))
-                       continue;
+       for (pfn = start_pfn; pfn < end_pfn; pfn += isolated) {
+               if (!pfn_valid(pfn) || zone != page_zone(pfn_to_page(pfn)))
+                       break;
 
                /*
-                * Check for overlapping nodes/zones. It's possible on some
-                * configurations to have a setup like
-                * node0 node1 node0
-                * i.e. it's possible that all pages within a zones range of
-                * pages do not belong to a single zone.
+                * On subsequent iterations ALIGN() is actually not needed,
+                * but we keep it that we not to complicate the code.
                 */
-               page = pfn_to_page(pfn);
-               if (page_zone(page) != zone)
-                       continue;
+               block_end_pfn = ALIGN(pfn + 1, pageblock_nr_pages);
+               block_end_pfn = min(block_end_pfn, end_pfn);
 
-               /* Check the block is suitable for migration */
-               if (!suitable_migration_target(page))
-                       continue;
+               spin_lock_irqsave(&zone->lock, flags);
+               isolated = isolate_freepages_block(pfn, block_end_pfn,
+                                                  &freelist, true);
+               spin_unlock_irqrestore(&zone->lock, flags);
 
                /*
-                * Found a block suitable for isolating free pages from. Now
-                * we disabled interrupts, double check things are ok and
-                * isolate the pages. This is to minimise the time IRQs
-                * are disabled
+                * In strict mode, isolate_freepages_block() returns 0 if
+                * there are any holes in the block (ie. invalid PFNs or
+                * non-free pages).
                 */
-               isolated = 0;
-               spin_lock_irqsave(&zone->lock, flags);
-               if (suitable_migration_target(page)) {
-                       isolated = isolate_freepages_block(zone, pfn, freelist);
-                       nr_freepages += isolated;
-               }
-               spin_unlock_irqrestore(&zone->lock, flags);
+               if (!isolated)
+                       break;
 
                /*
-                * Record the highest PFN we isolated pages from. When next
-                * looking for free pages, the search will restart here as
-                * page migration may have returned some pages to the allocator
+                * If we managed to isolate pages, it is always (1 << n) *
+                * pageblock_nr_pages for some non-negative n.  (Max order
+                * page may span two pageblocks).
                 */
-               if (isolated)
-                       high_pfn = max(high_pfn, pfn);
        }
 
        /* split_free_page does not map the pages */
-       list_for_each_entry(page, freelist, lru) {
-               arch_alloc_page(page, 0);
-               kernel_map_pages(page, 1, 1);
+       map_pages(&freelist);
+
+       if (pfn < end_pfn) {
+               /* Loop terminated early, cleanup. */
+               release_freepages(&freelist);
+               return 0;
        }
 
-       cc->free_pfn = high_pfn;
-       cc->nr_freepages = nr_freepages;
+       /* We don't use freelists for anything. */
+       return pfn;
 }
 
 /* Update the number of anon and file isolated pages in the zone */
@@ -243,38 +200,34 @@ static bool too_many_isolated(struct zone *zone)
        return isolated > (inactive + active) / 2;
 }
 
-/* possible outcome of isolate_migratepages */
-typedef enum {
-       ISOLATE_ABORT,          /* Abort compaction now */
-       ISOLATE_NONE,           /* No pages isolated, continue scanning */
-       ISOLATE_SUCCESS,        /* Pages isolated, migrate */
-} isolate_migrate_t;
-
-/*
- * Isolate all pages that can be migrated from the block pointed to by
- * the migrate scanner within compact_control.
+/**
+ * isolate_migratepages_range() - isolate all migrate-able pages in range.
+ * @zone:      Zone pages are in.
+ * @cc:                Compaction control structure.
+ * @low_pfn:   The first PFN of the range.
+ * @end_pfn:   The one-past-the-last PFN of the range.
+ *
+ * Isolate all pages that can be migrated from the range specified by
+ * [low_pfn, end_pfn).  Returns zero if there is a fatal signal
+ * pending), otherwise PFN of the first page that was not scanned
+ * (which may be both less, equal to or more then end_pfn).
+ *
+ * Assumes that cc->migratepages is empty and cc->nr_migratepages is
+ * zero.
+ *
+ * Apart from cc->migratepages and cc->nr_migratetypes this function
+ * does not modify any cc's fields, in particular it does not modify
+ * (or read for that matter) cc->migrate_pfn.
  */
-static isolate_migrate_t isolate_migratepages(struct zone *zone,
-                                       struct compact_control *cc)
+unsigned long
+isolate_migratepages_range(struct zone *zone, struct compact_control *cc,
+                          unsigned long low_pfn, unsigned long end_pfn)
 {
-       unsigned long low_pfn, end_pfn;
        unsigned long last_pageblock_nr = 0, pageblock_nr;
        unsigned long nr_scanned = 0, nr_isolated = 0;
        struct list_head *migratelist = &cc->migratepages;
        isolate_mode_t mode = ISOLATE_ACTIVE|ISOLATE_INACTIVE;
 
-       /* Do not scan outside zone boundaries */
-       low_pfn = max(cc->migrate_pfn, zone->zone_start_pfn);
-
-       /* Only scan within a pageblock boundary */
-       end_pfn = ALIGN(low_pfn + pageblock_nr_pages, pageblock_nr_pages);
-
-       /* Do not cross the free scanner or scan within a memory hole */
-       if (end_pfn > cc->free_pfn || !pfn_valid(low_pfn)) {
-               cc->migrate_pfn = end_pfn;
-               return ISOLATE_NONE;
-       }
-
        /*
         * Ensure that there are not too many pages isolated from the LRU
         * list by either parallel reclaimers or compaction. If there are,
@@ -283,12 +236,12 @@ static isolate_migrate_t isolate_migratepages(struct zone *zone,
        while (unlikely(too_many_isolated(zone))) {
                /* async migration should just abort */
                if (!cc->sync)
-                       return ISOLATE_ABORT;
+                       return 0;
 
                congestion_wait(BLK_RW_ASYNC, HZ/10);
 
                if (fatal_signal_pending(current))
-                       return ISOLATE_ABORT;
+                       return 0;
        }
 
        /* Time to isolate some pages for migration */
@@ -351,7 +304,7 @@ static isolate_migrate_t isolate_migratepages(struct zone *zone,
                 */
                pageblock_nr = low_pfn >> pageblock_order;
                if (!cc->sync && last_pageblock_nr != pageblock_nr &&
-                               get_pageblock_migratetype(page) != MIGRATE_MOVABLE) {
+                   !migrate_async_suitable(get_pageblock_migratetype(page))) {
                        low_pfn += pageblock_nr_pages;
                        low_pfn = ALIGN(low_pfn, pageblock_nr_pages) - 1;
                        last_pageblock_nr = pageblock_nr;
@@ -396,11 +349,124 @@ static isolate_migrate_t isolate_migratepages(struct zone *zone,
        acct_isolated(zone, cc);
 
        spin_unlock_irq(&zone->lru_lock);
-       cc->migrate_pfn = low_pfn;
 
        trace_mm_compaction_isolate_migratepages(nr_scanned, nr_isolated);
 
-       return ISOLATE_SUCCESS;
+       return low_pfn;
+}
+
+#endif /* CONFIG_COMPACTION || CONFIG_CMA */
+#ifdef CONFIG_COMPACTION
+
+/* Returns true if the page is within a block suitable for migration to */
+static bool suitable_migration_target(struct page *page)
+{
+
+       int migratetype = get_pageblock_migratetype(page);
+
+       /* Don't interfere with memory hot-remove or the min_free_kbytes blocks */
+       if (migratetype == MIGRATE_ISOLATE || migratetype == MIGRATE_RESERVE)
+               return false;
+
+       /* If the page is a large free page, then allow migration */
+       if (PageBuddy(page) && page_order(page) >= pageblock_order)
+               return true;
+
+       /* If the block is MIGRATE_MOVABLE or MIGRATE_CMA, allow migration */
+       if (migrate_async_suitable(migratetype))
+               return true;
+
+       /* Otherwise skip the block */
+       return false;
+}
+
+/*
+ * Based on information in the current compact_control, find blocks
+ * suitable for isolating free pages from and then isolate them.
+ */
+static void isolate_freepages(struct zone *zone,
+                               struct compact_control *cc)
+{
+       struct page *page;
+       unsigned long high_pfn, low_pfn, pfn, zone_end_pfn, end_pfn;
+       unsigned long flags;
+       int nr_freepages = cc->nr_freepages;
+       struct list_head *freelist = &cc->freepages;
+
+       /*
+        * Initialise the free scanner. The starting point is where we last
+        * scanned from (or the end of the zone if starting). The low point
+        * is the end of the pageblock the migration scanner is using.
+        */
+       pfn = cc->free_pfn;
+       low_pfn = cc->migrate_pfn + pageblock_nr_pages;
+
+       /*
+        * Take care that if the migration scanner is at the end of the zone
+        * that the free scanner does not accidentally move to the next zone
+        * in the next isolation cycle.
+        */
+       high_pfn = min(low_pfn, pfn);
+
+       zone_end_pfn = zone->zone_start_pfn + zone->spanned_pages;
+
+       /*
+        * Isolate free pages until enough are available to migrate the
+        * pages on cc->migratepages. We stop searching if the migrate
+        * and free page scanners meet or enough free pages are isolated.
+        */
+       for (; pfn > low_pfn && cc->nr_migratepages > nr_freepages;
+                                       pfn -= pageblock_nr_pages) {
+               unsigned long isolated;
+
+               if (!pfn_valid(pfn))
+                       continue;
+
+               /*
+                * Check for overlapping nodes/zones. It's possible on some
+                * configurations to have a setup like
+                * node0 node1 node0
+                * i.e. it's possible that all pages within a zones range of
+                * pages do not belong to a single zone.
+                */
+               page = pfn_to_page(pfn);
+               if (page_zone(page) != zone)
+                       continue;
+
+               /* Check the block is suitable for migration */
+               if (!suitable_migration_target(page))
+                       continue;
+
+               /*
+                * Found a block suitable for isolating free pages from. Now
+                * we disabled interrupts, double check things are ok and
+                * isolate the pages. This is to minimise the time IRQs
+                * are disabled
+                */
+               isolated = 0;
+               spin_lock_irqsave(&zone->lock, flags);
+               if (suitable_migration_target(page)) {
+                       end_pfn = min(pfn + pageblock_nr_pages, zone_end_pfn);
+                       isolated = isolate_freepages_block(pfn, end_pfn,
+                                                          freelist, false);
+                       nr_freepages += isolated;
+               }
+               spin_unlock_irqrestore(&zone->lock, flags);
+
+               /*
+                * Record the highest PFN we isolated pages from. When next
+                * looking for free pages, the search will restart here as
+                * page migration may have returned some pages to the allocator
+                */
+               if (isolated)
+                       high_pfn = max(high_pfn, pfn);
+       }
+
+       /* split_free_page does not map the pages */
+       map_pages(freelist);
+
+       cc->free_pfn = high_pfn;
+       cc->nr_freepages = nr_freepages;
 }
 
 /*
@@ -449,6 +515,44 @@ static void update_nr_listpages(struct compact_control *cc)
        cc->nr_freepages = nr_freepages;
 }
 
+/* possible outcome of isolate_migratepages */
+typedef enum {
+       ISOLATE_ABORT,          /* Abort compaction now */
+       ISOLATE_NONE,           /* No pages isolated, continue scanning */
+       ISOLATE_SUCCESS,        /* Pages isolated, migrate */
+} isolate_migrate_t;
+
+/*
+ * Isolate all pages that can be migrated from the block pointed to by
+ * the migrate scanner within compact_control.
+ */
+static isolate_migrate_t isolate_migratepages(struct zone *zone,
+                                       struct compact_control *cc)
+{
+       unsigned long low_pfn, end_pfn;
+
+       /* Do not scan outside zone boundaries */
+       low_pfn = max(cc->migrate_pfn, zone->zone_start_pfn);
+
+       /* Only scan within a pageblock boundary */
+       end_pfn = ALIGN(low_pfn + pageblock_nr_pages, pageblock_nr_pages);
+
+       /* Do not cross the free scanner or scan within a memory hole */
+       if (end_pfn > cc->free_pfn || !pfn_valid(low_pfn)) {
+               cc->migrate_pfn = end_pfn;
+               return ISOLATE_NONE;
+       }
+
+       /* Perform the isolation */
+       low_pfn = isolate_migratepages_range(zone, cc, low_pfn, end_pfn);
+       if (!low_pfn)
+               return ISOLATE_ABORT;
+
+       cc->migrate_pfn = low_pfn;
+
+       return ISOLATE_SUCCESS;
+}
+
 static int compact_finished(struct zone *zone,
                            struct compact_control *cc)
 {
@@ -795,3 +899,5 @@ void compaction_unregister_node(struct node *node)
        return device_remove_file(&node->dev, &dev_attr_compact);
 }
 #endif /* CONFIG_SYSFS && CONFIG_NUMA */
+
+#endif /* CONFIG_COMPACTION */
index 2189af4..aee4761 100644 (file)
@@ -100,6 +100,39 @@ extern void prep_compound_page(struct page *page, unsigned long order);
 extern bool is_free_buddy_page(struct page *page);
 #endif
 
+#if defined CONFIG_COMPACTION || defined CONFIG_CMA
+
+/*
+ * in mm/compaction.c
+ */
+/*
+ * compact_control is used to track pages being migrated and the free pages
+ * they are being migrated to during memory compaction. The free_pfn starts
+ * at the end of a zone and migrate_pfn begins at the start. Movable pages
+ * are moved to the end of a zone during a compaction run and the run
+ * completes when free_pfn <= migrate_pfn
+ */
+struct compact_control {
+       struct list_head freepages;     /* List of free pages to migrate to */
+       struct list_head migratepages;  /* List of pages being migrated */
+       unsigned long nr_freepages;     /* Number of isolated free pages */
+       unsigned long nr_migratepages;  /* Number of pages to migrate */
+       unsigned long free_pfn;         /* isolate_freepages search base */
+       unsigned long migrate_pfn;      /* isolate_migratepages search base */
+       bool sync;                      /* Synchronous migration */
+
+       int order;                      /* order a direct compactor needs */
+       int migratetype;                /* MOVABLE, RECLAIMABLE etc */
+       struct zone *zone;
+};
+
+unsigned long
+isolate_freepages_range(unsigned long start_pfn, unsigned long end_pfn);
+unsigned long
+isolate_migratepages_range(struct zone *zone, struct compact_control *cc,
+                          unsigned long low_pfn, unsigned long end_pfn);
+
+#endif
 
 /*
  * function for dealing with page's order in buddy system.
index 97cc273..c99ad4e 100644 (file)
@@ -1404,7 +1404,7 @@ static int get_any_page(struct page *p, unsigned long pfn, int flags)
                /* Not a free page */
                ret = 1;
        }
-       unset_migratetype_isolate(p);
+       unset_migratetype_isolate(p, MIGRATE_MOVABLE);
        unlock_memory_hotplug();
        return ret;
 }
index 6629faf..fc898cb 100644 (file)
@@ -891,7 +891,7 @@ static int __ref offline_pages(unsigned long start_pfn,
        nr_pages = end_pfn - start_pfn;
 
        /* set above range as isolated */
-       ret = start_isolate_page_range(start_pfn, end_pfn);
+       ret = start_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
        if (ret)
                goto out;
 
@@ -956,7 +956,7 @@ repeat:
           We cannot do rollback at this point. */
        offline_isolated_pages(start_pfn, end_pfn);
        /* reset pagetype flags and makes migrate type to be MOVABLE */
-       undo_isolate_page_range(start_pfn, end_pfn);
+       undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
        /* removal success */
        zone->present_pages -= offlined_pages;
        zone->zone_pgdat->node_present_pages -= offlined_pages;
@@ -981,7 +981,7 @@ failed_removal:
                start_pfn, end_pfn);
        memory_notify(MEM_CANCEL_OFFLINE, &arg);
        /* pushback to free area */
-       undo_isolate_page_range(start_pfn, end_pfn);
+       undo_isolate_page_range(start_pfn, end_pfn, MIGRATE_MOVABLE);
 
 out:
        unlock_memory_hotplug();
index 918330f..ed85c02 100644 (file)
@@ -57,6 +57,7 @@
 #include <linux/ftrace_event.h>
 #include <linux/memcontrol.h>
 #include <linux/prefetch.h>
+#include <linux/migrate.h>
 #include <linux/page-debug-flags.h>
 
 #include <asm/tlbflush.h>
@@ -513,10 +514,10 @@ static inline int page_is_buddy(struct page *page, struct page *buddy,
  * free pages of length of (1 << order) and marked with _mapcount -2. Page's
  * order is recorded in page_private(page) field.
  * So when we are allocating or freeing one, we can derive the state of the
- * other.  That is, if we allocate a small block, and both were   
- * free, the remainder of the region must be split into blocks.   
+ * other.  That is, if we allocate a small block, and both were
+ * free, the remainder of the region must be split into blocks.
  * If a block is freed, and its buddy is also free, then this
- * triggers coalescing into a block of larger size.            
+ * triggers coalescing into a block of larger size.
  *
  * -- wli
  */
@@ -749,6 +750,24 @@ void __meminit __free_pages_bootmem(struct page *page, unsigned int order)
        __free_pages(page, order);
 }
 
+#ifdef CONFIG_CMA
+/* Free whole pageblock and set it's migration type to MIGRATE_CMA. */
+void __init init_cma_reserved_pageblock(struct page *page)
+{
+       unsigned i = pageblock_nr_pages;
+       struct page *p = page;
+
+       do {
+               __ClearPageReserved(p);
+               set_page_count(p, 0);
+       } while (++p, --i);
+
+       set_page_refcounted(page);
+       set_pageblock_migratetype(page, MIGRATE_CMA);
+       __free_pages(page, pageblock_order);
+       totalram_pages += pageblock_nr_pages;
+}
+#endif
 
 /*
  * The order of subdivision here is critical for the IO subsystem.
@@ -874,11 +893,17 @@ struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
  * This array describes the order lists are fallen back to when
  * the free lists for the desirable migrate type are depleted
  */
-static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
-       [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
-       [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_RESERVE },
-       [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
-       [MIGRATE_RESERVE]     = { MIGRATE_RESERVE,     MIGRATE_RESERVE,   MIGRATE_RESERVE }, /* Never used */
+static int fallbacks[MIGRATE_TYPES][4] = {
+       [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,     MIGRATE_RESERVE },
+       [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,     MIGRATE_RESERVE },
+#ifdef CONFIG_CMA
+       [MIGRATE_MOVABLE]     = { MIGRATE_CMA,         MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
+       [MIGRATE_CMA]         = { MIGRATE_RESERVE }, /* Never used */
+#else
+       [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE,   MIGRATE_RESERVE },
+#endif
+       [MIGRATE_RESERVE]     = { MIGRATE_RESERVE }, /* Never used */
+       [MIGRATE_ISOLATE]     = { MIGRATE_RESERVE }, /* Never used */
 };
 
 /*
@@ -973,12 +998,12 @@ __rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
        /* Find the largest possible block of pages in the other list */
        for (current_order = MAX_ORDER-1; current_order >= order;
                                                --current_order) {
-               for (i = 0; i < MIGRATE_TYPES - 1; i++) {
+               for (i = 0;; i++) {
                        migratetype = fallbacks[start_migratetype][i];
 
                        /* MIGRATE_RESERVE handled later if necessary */
                        if (migratetype == MIGRATE_RESERVE)
-                               continue;
+                               break;
 
                        area = &(zone->free_area[current_order]);
                        if (list_empty(&area->free_list[migratetype]))
@@ -993,11 +1018,18 @@ __rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
                         * pages to the preferred allocation list. If falling
                         * back for a reclaimable kernel allocation, be more
                         * aggressive about taking ownership of free pages
+                        *
+                        * On the other hand, never change migration
+                        * type of MIGRATE_CMA pageblocks nor move CMA
+                        * pages on different free lists. We don't
+                        * want unmovable pages to be allocated from
+                        * MIGRATE_CMA areas.
                         */
-                       if (unlikely(current_order >= (pageblock_order >> 1)) ||
-                                       start_migratetype == MIGRATE_RECLAIMABLE ||
-                                       page_group_by_mobility_disabled) {
-                               unsigned long pages;
+                       if (!is_migrate_cma(migratetype) &&
+                           (unlikely(current_order >= pageblock_order / 2) ||
+                            start_migratetype == MIGRATE_RECLAIMABLE ||
+                            page_group_by_mobility_disabled)) {
+                               int pages;
                                pages = move_freepages_block(zone, page,
                                                                start_migratetype);
 
@@ -1015,11 +1047,14 @@ __rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
                        rmv_page_order(page);
 
                        /* Take ownership for orders >= pageblock_order */
-                       if (current_order >= pageblock_order)
+                       if (current_order >= pageblock_order &&
+                           !is_migrate_cma(migratetype))
                                change_pageblock_range(page, current_order,
                                                        start_migratetype);
 
-                       expand(zone, page, order, current_order, area, migratetype);
+                       expand(zone, page, order, current_order, area,
+                              is_migrate_cma(migratetype)
+                            ? migratetype : start_migratetype);
 
                        trace_mm_page_alloc_extfrag(page, order, current_order,
                                start_migratetype, migratetype);
@@ -1061,17 +1096,17 @@ retry_reserve:
        return page;
 }
 
-/* 
+/*
  * Obtain a specified number of elements from the buddy allocator, all under
  * a single hold of the lock, for efficiency.  Add them to the supplied list.
  * Returns the number of new pages which were placed at *list.
  */
-static int rmqueue_bulk(struct zone *zone, unsigned int order, 
+static int rmqueue_bulk(struct zone *zone, unsigned int order,
                        unsigned long count, struct list_head *list,
                        int migratetype, int cold)
 {
-       int i;
-       
+       int mt = migratetype, i;
+
        spin_lock(&zone->lock);
        for (i = 0; i < count; ++i) {
                struct page *page = __rmqueue(zone, order, migratetype);
@@ -1091,7 +1126,12 @@ static int rmqueue_bulk(struct zone *zone, unsigned int order,
                        list_add(&page->lru, list);
                else
                        list_add_tail(&page->lru, list);
-               set_page_private(page, migratetype);
+               if (IS_ENABLED(CONFIG_CMA)) {
+                       mt = get_pageblock_migratetype(page);
+                       if (!is_migrate_cma(mt) && mt != MIGRATE_ISOLATE)
+                               mt = migratetype;
+               }
+               set_page_private(page, mt);
                list = &page->lru;
        }
        __mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order));
@@ -1371,8 +1411,12 @@ int split_free_page(struct page *page)
 
        if (order >= pageblock_order - 1) {
                struct page *endpage = page + (1 << order) - 1;
-               for (; page < endpage; page += pageblock_nr_pages)
-                       set_pageblock_migratetype(page, MIGRATE_MOVABLE);
+               for (; page < endpage; page += pageblock_nr_pages) {
+                       int mt = get_pageblock_migratetype(page);
+                       if (mt != MIGRATE_ISOLATE && !is_migrate_cma(mt))
+                               set_pageblock_migratetype(page,
+                                                         MIGRATE_MOVABLE);
+               }
        }
 
        return 1 << order;
@@ -2086,16 +2130,13 @@ __alloc_pages_direct_compact(gfp_t gfp_mask, unsigned int order,
 }
 #endif /* CONFIG_COMPACTION */
 
-/* The really slow allocator path where we enter direct reclaim */
-static inline struct page *
-__alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
-       struct zonelist *zonelist, enum zone_type high_zoneidx,
-       nodemask_t *nodemask, int alloc_flags, struct zone *preferred_zone,
-       int migratetype, unsigned long *did_some_progress)
+/* Perform direct synchronous page reclaim */
+static int
+__perform_reclaim(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist,
+                 nodemask_t *nodemask)
 {
-       struct page *page = NULL;
        struct reclaim_state reclaim_state;
-       bool drained = false;
+       int progress;
 
        cond_resched();
 
@@ -2106,7 +2147,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
        reclaim_state.reclaimed_slab = 0;
        current->reclaim_state = &reclaim_state;
 
-       *did_some_progress = try_to_free_pages(zonelist, order, gfp_mask, nodemask);
+       progress = try_to_free_pages(zonelist, order, gfp_mask, nodemask);
 
        current->reclaim_state = NULL;
        lockdep_clear_current_reclaim_state();
@@ -2114,6 +2155,21 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
 
        cond_resched();
 
+       return progress;
+}
+
+/* The really slow allocator path where we enter direct reclaim */
+static inline struct page *
+__alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
+       struct zonelist *zonelist, enum zone_type high_zoneidx,
+       nodemask_t *nodemask, int alloc_flags, struct zone *preferred_zone,
+       int migratetype, unsigned long *did_some_progress)
+{
+       struct page *page = NULL;
+       bool drained = false;
+
+       *did_some_progress = __perform_reclaim(gfp_mask, order, zonelist,
+                                              nodemask);
        if (unlikely(!(*did_some_progress)))
                return NULL;
 
@@ -4301,7 +4357,7 @@ static void __paginginit free_area_init_core(struct pglist_data *pgdat,
        init_waitqueue_head(&pgdat->kswapd_wait);
        pgdat->kswapd_max_order = 0;
        pgdat_page_cgroup_init(pgdat);
-       
+
        for (j = 0; j < MAX_NR_ZONES; j++) {
                struct zone *zone = pgdat->node_zones + j;
                unsigned long size, realsize, memmap_pages;
@@ -4976,14 +5032,7 @@ static void setup_per_zone_lowmem_reserve(void)
        calculate_totalreserve_pages();
 }
 
-/**
- * setup_per_zone_wmarks - called when min_free_kbytes changes
- * or when memory is hot-{added|removed}
- *
- * Ensures that the watermark[min,low,high] values for each zone are set
- * correctly with respect to min_free_kbytes.
- */
-void setup_per_zone_wmarks(void)
+static void __setup_per_zone_wmarks(void)
 {
        unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
        unsigned long lowmem_pages = 0;
@@ -5030,6 +5079,11 @@ void setup_per_zone_wmarks(void)
 
                zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);
                zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
+
+               zone->watermark[WMARK_MIN] += cma_wmark_pages(zone);
+               zone->watermark[WMARK_LOW] += cma_wmark_pages(zone);
+               zone->watermark[WMARK_HIGH] += cma_wmark_pages(zone);
+
                setup_zone_migrate_reserve(zone);
                spin_unlock_irqrestore(&zone->lock, flags);
        }
@@ -5038,6 +5092,20 @@ void setup_per_zone_wmarks(void)
        calculate_totalreserve_pages();
 }
 
+/**
+ * setup_per_zone_wmarks - called when min_free_kbytes changes
+ * or when memory is hot-{added|removed}
+ *
+ * Ensures that the watermark[min,low,high] values for each zone are set
+ * correctly with respect to min_free_kbytes.
+ */
+void setup_per_zone_wmarks(void)
+{
+       mutex_lock(&zonelists_mutex);
+       __setup_per_zone_wmarks();
+       mutex_unlock(&zonelists_mutex);
+}
+
 /*
  * The inactive anon list should be small enough that the VM never has to
  * do too much work, but large enough that each inactive page has a chance
@@ -5412,14 +5480,16 @@ static int
 __count_immobile_pages(struct zone *zone, struct page *page, int count)
 {
        unsigned long pfn, iter, found;
+       int mt;
+
        /*
         * For avoiding noise data, lru_add_drain_all() should be called
         * If ZONE_MOVABLE, the zone never contains immobile pages
         */
        if (zone_idx(zone) == ZONE_MOVABLE)
                return true;
-
-       if (get_pageblock_migratetype(page) == MIGRATE_MOVABLE)
+       mt = get_pageblock_migratetype(page);
+       if (mt == MIGRATE_MOVABLE || is_migrate_cma(mt))
                return true;
 
        pfn = page_to_pfn(page);
@@ -5536,7 +5606,7 @@ out:
        return ret;
 }
 
-void unset_migratetype_isolate(struct page *page)
+void unset_migratetype_isolate(struct page *page, unsigned migratetype)
 {
        struct zone *zone;
        unsigned long flags;
@@ -5544,12 +5614,259 @@ void unset_migratetype_isolate(struct page *page)
        spin_lock_irqsave(&zone->lock, flags);
        if (get_pageblock_migratetype(page) != MIGRATE_ISOLATE)
                goto out;
-       set_pageblock_migratetype(page, MIGRATE_MOVABLE);
-       move_freepages_block(zone, page, MIGRATE_MOVABLE);
+       set_pageblock_migratetype(page, migratetype);
+       move_freepages_block(zone, page, migratetype);
 out:
        spin_unlock_irqrestore(&zone->lock, flags);
 }
 
+#ifdef CONFIG_CMA
+
+static unsigned long pfn_max_align_down(unsigned long pfn)
+{
+       return pfn & ~(max_t(unsigned long, MAX_ORDER_NR_PAGES,
+                            pageblock_nr_pages) - 1);
+}
+
+static unsigned long pfn_max_align_up(unsigned long pfn)
+{
+       return ALIGN(pfn, max_t(unsigned long, MAX_ORDER_NR_PAGES,
+                               pageblock_nr_pages));
+}
+
+static struct page *
+__alloc_contig_migrate_alloc(struct page *page, unsigned long private,
+                            int **resultp)
+{
+       return alloc_page(GFP_HIGHUSER_MOVABLE);
+}
+
+/* [start, end) must belong to a single zone. */
+static int __alloc_contig_migrate_range(unsigned long start, unsigned long end)
+{
+       /* This function is based on compact_zone() from compaction.c. */
+
+       unsigned long pfn = start;
+       unsigned int tries = 0;
+       int ret = 0;
+
+       struct compact_control cc = {
+               .nr_migratepages = 0,
+               .order = -1,
+               .zone = page_zone(pfn_to_page(start)),
+               .sync = true,
+       };
+       INIT_LIST_HEAD(&cc.migratepages);
+
+       migrate_prep_local();
+
+       while (pfn < end || !list_empty(&cc.migratepages)) {
+               if (fatal_signal_pending(current)) {
+                       ret = -EINTR;
+                       break;
+               }
+
+               if (list_empty(&cc.migratepages)) {
+                       cc.nr_migratepages = 0;
+                       pfn = isolate_migratepages_range(cc.zone, &cc,
+                                                        pfn, end);
+                       if (!pfn) {
+                               ret = -EINTR;
+                               break;
+                       }
+                       tries = 0;
+               } else if (++tries == 5) {
+                       ret = ret < 0 ? ret : -EBUSY;
+                       break;
+               }
+
+               ret = migrate_pages(&cc.migratepages,
+                                   __alloc_contig_migrate_alloc,
+                                   0, false, MIGRATE_SYNC);
+       }
+
+       putback_lru_pages(&cc.migratepages);
+       return ret > 0 ? 0 : ret;
+}
+
+/*
+ * Update zone's cma pages counter used for watermark level calculation.
+ */
+static inline void __update_cma_watermarks(struct zone *zone, int count)
+{
+       unsigned long flags;
+       spin_lock_irqsave(&zone->lock, flags);
+       zone->min_cma_pages += count;
+       spin_unlock_irqrestore(&zone->lock, flags);
+       setup_per_zone_wmarks();
+}
+
+/*
+ * Trigger memory pressure bump to reclaim some pages in order to be able to
+ * allocate 'count' pages in single page units. Does similar work as
+ *__alloc_pages_slowpath() function.
+ */
+static int __reclaim_pages(struct zone *zone, gfp_t gfp_mask, int count)
+{
+       enum zone_type high_zoneidx = gfp_zone(gfp_mask);
+       struct zonelist *zonelist = node_zonelist(0, gfp_mask);
+       int did_some_progress = 0;
+       int order = 1;
+
+       /*
+        * Increase level of watermarks to force kswapd do his job
+        * to stabilise at new watermark level.
+        */
+       __update_cma_watermarks(zone, count);
+
+       /* Obey watermarks as if the page was being allocated */
+       while (!zone_watermark_ok(zone, 0, low_wmark_pages(zone), 0, 0)) {
+               wake_all_kswapd(order, zonelist, high_zoneidx, zone_idx(zone));
+
+               did_some_progress = __perform_reclaim(gfp_mask, order, zonelist,
+                                                     NULL);
+               if (!did_some_progress) {
+                       /* Exhausted what can be done so it's blamo time */
+                       out_of_memory(zonelist, gfp_mask, order, NULL, false);
+               }
+       }
+
+       /* Restore original watermark levels. */
+       __update_cma_watermarks(zone, -count);
+
+       return count;
+}
+
+/**
+ * alloc_contig_range() -- tries to allocate given range of pages
+ * @start:     start PFN to allocate
+ * @end:       one-past-the-last PFN to allocate
+ * @migratetype:       migratetype of the underlaying pageblocks (either
+ *                     #MIGRATE_MOVABLE or #MIGRATE_CMA).  All pageblocks
+ *                     in range must have the same migratetype and it must
+ *                     be either of the two.
+ *
+ * The PFN range does not have to be pageblock or MAX_ORDER_NR_PAGES
+ * aligned, however it's the caller's responsibility to guarantee that
+ * we are the only thread that changes migrate type of pageblocks the
+ * pages fall in.
+ *
+ * The PFN range must belong to a single zone.
+ *
+ * Returns zero on success or negative error code.  On success all
+ * pages which PFN is in [start, end) are allocated for the caller and
+ * need to be freed with free_contig_range().
+ */
+int alloc_contig_range(unsigned long start, unsigned long end,
+                      unsigned migratetype)
+{
+       struct zone *zone = page_zone(pfn_to_page(start));
+       unsigned long outer_start, outer_end;
+       int ret = 0, order;
+
+       /*
+        * What we do here is we mark all pageblocks in range as
+        * MIGRATE_ISOLATE.  Because pageblock and max order pages may
+        * have different sizes, and due to the way page allocator
+        * work, we align the range to biggest of the two pages so
+        * that page allocator won't try to merge buddies from
+        * different pageblocks and change MIGRATE_ISOLATE to some
+        * other migration type.
+        *
+        * Once the pageblocks are marked as MIGRATE_ISOLATE, we
+        * migrate the pages from an unaligned range (ie. pages that
+        * we are interested in).  This will put all the pages in
+        * range back to page allocator as MIGRATE_ISOLATE.
+        *
+        * When this is done, we take the pages in range from page
+        * allocator removing them from the buddy system.  This way
+        * page allocator will never consider using them.
+        *
+        * This lets us mark the pageblocks back as
+        * MIGRATE_CMA/MIGRATE_MOVABLE so that free pages in the
+        * aligned range but not in the unaligned, original range are
+        * put back to page allocator so that buddy can use them.
+        */
+
+       ret = start_isolate_page_range(pfn_max_align_down(start),
+                                      pfn_max_align_up(end), migratetype);
+       if (ret)
+               goto done;
+
+       ret = __alloc_contig_migrate_range(start, end);
+       if (ret)
+               goto done;
+
+       /*
+        * Pages from [start, end) are within a MAX_ORDER_NR_PAGES
+        * aligned blocks that are marked as MIGRATE_ISOLATE.  What's
+        * more, all pages in [start, end) are free in page allocator.
+        * What we are going to do is to allocate all pages from
+        * [start, end) (that is remove them from page allocator).
+        *
+        * The only problem is that pages at the beginning and at the
+        * end of interesting range may be not aligned with pages that
+        * page allocator holds, ie. they can be part of higher order
+        * pages.  Because of this, we reserve the bigger range and
+        * once this is done free the pages we are not interested in.
+        *
+        * We don't have to hold zone->lock here because the pages are
+        * isolated thus they won't get removed from buddy.
+        */
+
+       lru_add_drain_all();
+       drain_all_pages();
+
+       order = 0;
+       outer_start = start;
+       while (!PageBuddy(pfn_to_page(outer_start))) {
+               if (++order >= MAX_ORDER) {
+                       ret = -EBUSY;
+                       goto done;
+               }
+               outer_start &= ~0UL << order;
+       }
+
+       /* Make sure the range is really isolated. */
+       if (test_pages_isolated(outer_start, end)) {
+               pr_warn("alloc_contig_range test_pages_isolated(%lx, %lx) failed\n",
+                      outer_start, end);
+               ret = -EBUSY;
+               goto done;
+       }
+
+       /*
+        * Reclaim enough pages to make sure that contiguous allocation
+        * will not starve the system.
+        */
+       __reclaim_pages(zone, GFP_HIGHUSER_MOVABLE, end-start);
+
+       /* Grab isolated pages from freelists. */
+       outer_end = isolate_freepages_range(outer_start, end);
+       if (!outer_end) {
+               ret = -EBUSY;
+               goto done;
+       }
+
+       /* Free head and tail (if any) */
+       if (start != outer_start)
+               free_contig_range(outer_start, start - outer_start);
+       if (end != outer_end)
+               free_contig_range(end, outer_end - end);
+
+done:
+       undo_isolate_page_range(pfn_max_align_down(start),
+                               pfn_max_align_up(end), migratetype);
+       return ret;
+}
+
+void free_contig_range(unsigned long pfn, unsigned nr_pages)
+{
+       for (; nr_pages--; ++pfn)
+               __free_page(pfn_to_page(pfn));
+}
+#endif
+
 #ifdef CONFIG_MEMORY_HOTREMOVE
 /*
  * All pages in the range must be isolated before calling this.
index 4ae42bb..c9f0477 100644 (file)
@@ -24,6 +24,7 @@ __first_valid_page(unsigned long pfn, unsigned long nr_pages)
  * to be MIGRATE_ISOLATE.
  * @start_pfn: The lower PFN of the range to be isolated.
  * @end_pfn: The upper PFN of the range to be isolated.
+ * @migratetype: migrate type to set in error recovery.
  *
  * Making page-allocation-type to be MIGRATE_ISOLATE means free pages in
  * the range will never be allocated. Any free pages and pages freed in the
@@ -32,8 +33,8 @@ __first_valid_page(unsigned long pfn, unsigned long nr_pages)
  * start_pfn/end_pfn must be aligned to pageblock_order.
  * Returns 0 on success and -EBUSY if any part of range cannot be isolated.
  */
-int
-start_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn)
+int start_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn,
+                            unsigned migratetype)
 {
        unsigned long pfn;
        unsigned long undo_pfn;
@@ -56,7 +57,7 @@ undo:
        for (pfn = start_pfn;
             pfn < undo_pfn;
             pfn += pageblock_nr_pages)
-               unset_migratetype_isolate(pfn_to_page(pfn));
+               unset_migratetype_isolate(pfn_to_page(pfn), migratetype);
 
        return -EBUSY;
 }
@@ -64,8 +65,8 @@ undo:
 /*
  * Make isolated pages available again.
  */
-int
-undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn)
+int undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn,
+                           unsigned migratetype)
 {
        unsigned long pfn;
        struct page *page;
@@ -77,7 +78,7 @@ undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn)
                page = __first_valid_page(pfn, pageblock_nr_pages);
                if (!page || get_pageblock_migratetype(page) != MIGRATE_ISOLATE)
                        continue;
-               unset_migratetype_isolate(page);
+               unset_migratetype_isolate(page, migratetype);
        }
        return 0;
 }
@@ -86,7 +87,7 @@ undo_isolate_page_range(unsigned long start_pfn, unsigned long end_pfn)
  * all pages in [start_pfn...end_pfn) must be in the same zone.
  * zone->lock must be held before call this.
  *
- * Returns 1 if all pages in the range is isolated.
+ * Returns 1 if all pages in the range are isolated.
  */
 static int
 __test_page_isolated_in_pageblock(unsigned long pfn, unsigned long end_pfn)
index 94dff88..9c13bab 100644 (file)
@@ -1279,7 +1279,7 @@ DEFINE_RWLOCK(vmlist_lock);
 struct vm_struct *vmlist;
 
 static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,
-                             unsigned long flags, void *caller)
+                             unsigned long flags, const void *caller)
 {
        vm->flags = flags;
        vm->addr = (void *)va->va_start;
@@ -1305,7 +1305,7 @@ static void insert_vmalloc_vmlist(struct vm_struct *vm)
 }
 
 static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,
-                             unsigned long flags, void *caller)
+                             unsigned long flags, const void *caller)
 {
        setup_vmalloc_vm(vm, va, flags, caller);
        insert_vmalloc_vmlist(vm);
@@ -1313,7 +1313,7 @@ static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,
 
 static struct vm_struct *__get_vm_area_node(unsigned long size,
                unsigned long align, unsigned long flags, unsigned long start,
-               unsigned long end, int node, gfp_t gfp_mask, void *caller)
+               unsigned long end, int node, gfp_t gfp_mask, const void *caller)
 {
        struct vmap_area *va;
        struct vm_struct *area;
@@ -1374,7 +1374,7 @@ EXPORT_SYMBOL_GPL(__get_vm_area);
 
 struct vm_struct *__get_vm_area_caller(unsigned long size, unsigned long flags,
                                       unsigned long start, unsigned long end,
-                                      void *caller)
+                                      const void *caller)
 {
        return __get_vm_area_node(size, 1, flags, start, end, -1, GFP_KERNEL,
                                  caller);
@@ -1396,13 +1396,21 @@ struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
 }
 
 struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags,
-                               void *caller)
+                               const void *caller)
 {
        return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
                                                -1, GFP_KERNEL, caller);
 }
 
-static struct vm_struct *find_vm_area(const void *addr)
+/**
+ *     find_vm_area  -  find a continuous kernel virtual area
+ *     @addr:          base address
+ *
+ *     Search for the kernel VM area starting at @addr, and return it.
+ *     It is up to the caller to do all required locking to keep the returned
+ *     pointer valid.
+ */
+struct vm_struct *find_vm_area(const void *addr)
 {
        struct vmap_area *va;
 
@@ -1567,9 +1575,9 @@ EXPORT_SYMBOL(vmap);
 
 static void *__vmalloc_node(unsigned long size, unsigned long align,
                            gfp_t gfp_mask, pgprot_t prot,
-                           int node, void *caller);
+                           int node, const void *caller);
 static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
-                                pgprot_t prot, int node, void *caller)
+                                pgprot_t prot, int node, const void *caller)
 {
        const int order = 0;
        struct page **pages;
@@ -1642,7 +1650,7 @@ fail:
  */
 void *__vmalloc_node_range(unsigned long size, unsigned long align,
                        unsigned long start, unsigned long end, gfp_t gfp_mask,
-                       pgprot_t prot, int node, void *caller)
+                       pgprot_t prot, int node, const void *caller)
 {
        struct vm_struct *area;
        void *addr;
@@ -1698,7 +1706,7 @@ fail:
  */
 static void *__vmalloc_node(unsigned long size, unsigned long align,
                            gfp_t gfp_mask, pgprot_t prot,
-                           int node, void *caller)
+                           int node, const void *caller)
 {
        return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                                gfp_mask, prot, node, caller);
@@ -2574,6 +2582,9 @@ static int s_show(struct seq_file *m, void *p)
        if (v->flags & VM_IOREMAP)
                seq_printf(m, " ioremap");
 
+       if (v->flags & VM_DMA)
+               seq_printf(m, " dma");
+
        if (v->flags & VM_ALLOC)
                seq_printf(m, " vmalloc");
 
index 7db1b9b..0dad31d 100644 (file)
@@ -613,6 +613,9 @@ static char * const migratetype_names[MIGRATE_TYPES] = {
        "Reclaimable",
        "Movable",
        "Reserve",
+#ifdef CONFIG_CMA
+       "CMA",
+#endif
        "Isolate",
 };