From: Michal Nazarewicz <m.nazarew...@samsung.com>

This commits adds vcm_phys_alloc() function with some
accompanying functions which allocates physical memory.  This
should be used from withing alloc or phys callback of a VCM
driver if one does not want to provide its own allocator.

Signed-off-by: Michal Nazarewicz <m.nazarew...@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.p...@samsung.com>
---
 Documentation/virtual-contiguous-memory.txt |   31 ++++
 include/linux/vcm-drv.h                     |   88 ++++++++++
 mm/Kconfig                                  |    9 +
 mm/vcm.c                                    |  249 +++++++++++++++++++++++++++
 4 files changed, 377 insertions(+), 0 deletions(-)

diff --git a/Documentation/virtual-contiguous-memory.txt 
b/Documentation/virtual-contiguous-memory.txt
index 2008465..10a0638 100644
--- a/Documentation/virtual-contiguous-memory.txt
+++ b/Documentation/virtual-contiguous-memory.txt
@@ -672,6 +672,37 @@ Both phys and alloc callbacks need to provide a free 
callbakc along
 with the vc_phys structure, which will, as one may imagine, free
 allocated space when user calls vcm_free().
 
+Unless VCM driver needs some special handling of physical memory, the
+vcm_phys_alloc() function can be used:
+
+       struct vcm_phys *__must_check
+       vcm_phys_alloc(resource_size_t size, unsigned flags,
+                      const unsigned char *orders);
+
+The last argument of this function (orders) is an array of orders of
+page sizes that function should try to allocate.  This array must be
+sorted from highest order to lowest and the last entry must be zero.
+
+For instance, an array { 8, 4, 0 } means that the function should try
+and allocate 1MiB, 64KiB and 4KiB pages (this is assuming PAGE_SIZE is
+4KiB which is true for all supported architectures).  For example, if
+requested size is 2MiB and 68 KiB, the function will try to allocate
+two 1MiB pages, one 64KiB page and one 4KiB page.  This may be useful
+when the mapping is written to the MMU since the largest possible
+pages will be used reducing the number of entries.
+
+The function allocates memory from DMA32 zone.  If driver has some
+other requirements (that is require different GFP flags) it can use
+__vcm_phys_alloc() function which, besides arguments that
+vcm_phys_alloc() accepts, take GFP flags as the last argument:
+
+       struct vcm_phys *__must_check
+       __vcm_phys_alloc(resource_size_t size, unsigned flags,
+                        const unsigned char *orders, gfp_t gfp);
+
+However, if those functions are used, VCM driver needs to select an
+VCM_PHYS Kconfig option or oterwise they won't be available.
+
 All those operations may assume that size is a non-zero and divisible
 by PAGE_SIZE.
 
diff --git a/include/linux/vcm-drv.h b/include/linux/vcm-drv.h
index d7ae660..536b051 100644
--- a/include/linux/vcm-drv.h
+++ b/include/linux/vcm-drv.h
@@ -114,4 +114,92 @@ struct vcm_phys {
  */
 struct vcm *__must_check vcm_init(struct vcm *vcm);
 
+#ifdef CONFIG_VCM_PHYS
+
+/**
+ * __vcm_phys_alloc() - allocates physical discontiguous space
+ * @size:      size of the block to allocate.
+ * @flags:     additional allocation flags; XXX FIXME: document
+ * @orders:    array of orders of pages supported by the MMU sorted from
+ *             the largest to the smallest.  The last element is always
+ *             zero (which means 4K page).
+ * @gfp:       the gfp flags for pages to allocate.
+ *
+ * This function tries to allocate a physical discontiguous space in
+ * such a way that it allocates the largest possible blocks from the
+ * sizes donated by the @orders array.  So if @orders is { 8, 0 }
+ * (which means 1MiB and 4KiB pages are to be used) and requested
+ * @size is 2MiB and 12KiB the function will try to allocate two 1MiB
+ * pages and three 4KiB pages (in that order).  If big page cannot be
+ * allocated the function will still try to allocate more smaller
+ * pages.
+ */
+struct vcm_phys *__must_check
+__vcm_phys_alloc(resource_size_t size, unsigned flags,
+                const unsigned char *orders, gfp_t gfp);
+
+/**
+ * vcm_phys_alloc() - allocates physical discontiguous space
+ * @size:      size of the block to allocate.
+ * @flags:     additional allocation flags; XXX FIXME: document
+ * @orders:    array of orders of pages supported by the MMU sorted from
+ *             the largest to the smallest.  The last element is always
+ *             zero (which means 4K page).
+ *
+ * This function tries to allocate a physical discontiguous space in
+ * such a way that it allocates the largest possible blocks from the
+ * sizes donated by the @orders array.  So if @orders is { 8, 0 }
+ * (which means 1MiB and 4KiB pages are to be used) and requested
+ * @size is 2MiB and 12KiB the function will try to allocate two 1MiB
+ * pages and three 4KiB pages (in that order).  If big page cannot be
+ * allocated the function will still try to allocate more smaller
+ * pages.
+ */
+static inline struct vcm_phys *__must_check
+vcm_phys_alloc(resource_size_t size, unsigned flags,
+              const unsigned char *orders) {
+       return __vcm_phys_alloc(size, flags, orders, GFP_DMA32);
+}
+
+/**
+ * vcm_phys_walk() - helper function for mapping physical pages
+ * @vaddr:     virtual address to map/unmap physical space to/from
+ * @phys:      physical space
+ * @orders:    array of orders of pages supported by the MMU sorted from
+ *             the largest to the smallest.  The last element is always
+ *             zero (which means 4K page).
+ * @callback:  function called for each page.
+ * @recover:   function called for each page when @callback returns
+ *             negative number; if it also returns negative number
+ *             function terminates; may be NULL.
+ * @priv:      private data for the callbacks.
+ *
+ * This function walks through @phys trying to mach largest possible
+ * page size donated by @orders.  For each such page @callback is
+ * called.  If @callback returns negative number the function calls
+ * @recover for each page @callback was called successfully.
+ *
+ * So, for instance, if we have a physical memory which consist of
+ * 1Mib part and 8KiB part and @orders is { 8, 0 } (which means 1MiB
+ * and 4KiB pages are to be used), @callback will be called first with
+ * 1MiB page and then two times with 4KiB page.  This is of course
+ * provided that @vaddr has correct alignment.
+ *
+ * The idea is for hardware MMU drivers to call this function and
+ * provide a callbacks for mapping/unmapping a single page.  The
+ * function divides the region into pages that the MMU can handle.
+ *
+ * If @callback at one point returns a negative number this is the
+ * return value of the function; otherwise zero is returned.
+ */
+int vcm_phys_walk(dma_addr_t vaddr, const struct vcm_phys *phys,
+                 const unsigned char *orders,
+                 int (*callback)(dma_addr_t vaddr, dma_addr_t paddr,
+                                 unsigned order, void *priv),
+                 int (*recovery)(dma_addr_t vaddr, dma_addr_t paddr,
+                                 unsigned order, void *priv),
+                 void *priv);
+
+#endif
+
 #endif
diff --git a/mm/Kconfig b/mm/Kconfig
index b937f32..00d975e 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -360,6 +360,15 @@ config VCM_RES_REFCNT
          This enables reference counting on a reservation to make sharing
          and migrating the ownership of the reservation easier.
 
+config VCM_PHYS
+       bool "VCM physical allocation wrappers"
+       depends on VCM && MODULES
+       help
+         This enables the vcm_phys family of functions provided for VCM
+         drivers.  If a VCM driver is built that requires this option, it
+         will be automatically selected.  You select it if you are going to
+         build external modules that will use this functionality.
+
 #
 # UP and nommu archs use km based percpu allocator
 #
diff --git a/mm/vcm.c b/mm/vcm.c
index 5819f0f..6804114 100644
--- a/mm/vcm.c
+++ b/mm/vcm.c
@@ -319,3 +319,252 @@ struct vcm *__must_check vcm_init(struct vcm *vcm)
        return vcm;
 }
 EXPORT_SYMBOL_GPL(vcm_init);
+
+
+/************************ Physical memory management ************************/
+
+#ifdef CONFIG_VCM_PHYS
+
+struct vcm_phys_list {
+       struct vcm_phys_list    *next;
+       unsigned                count;
+       struct vcm_phys_part    parts[31];
+};
+
+static struct vcm_phys_list *__must_check
+vcm_phys_alloc_list_order(struct vcm_phys_list *last, resource_size_t *pages,
+                         unsigned flags, unsigned order, unsigned *total,
+                         gfp_t gfp)
+{
+       unsigned count;
+
+       count   = *pages >> order;
+
+       do {
+               struct page *page = alloc_pages(gfp, order);
+
+               if (!page)
+                       /*
+                        * If allocation failed we may still
+                        * try to continua allocating smaller
+                        * pages.
+                        */
+                       break;
+
+               if (last->count == ARRAY_SIZE(last->parts)) {
+                       struct vcm_phys_list *l;
+                       l = kmalloc(sizeof *l, GFP_KERNEL);
+                       if (!l)
+                               return NULL;
+
+                       l->next = NULL;
+                       l->count = 0;
+                       last->next = l;
+                       last = l;
+               }
+
+               last->parts[last->count].start = page_to_phys(page);
+               last->parts[last->count].size  = (1 << order);
+               last->parts[last->count].page  = page;
+               ++last->count;
+               ++*total;
+               *pages -= 1 << order;
+       } while (--count);
+
+       return last;
+}
+
+static unsigned __must_check
+vcm_phys_alloc_list(struct vcm_phys_list *first,
+                   resource_size_t size, unsigned flags,
+                   const unsigned char *orders, gfp_t gfp)
+{
+       struct vcm_phys_list *last = first;
+       unsigned total_parts = 0;
+       resource_size_t pages;
+
+       /*
+        * We are trying to allocate as large pages as possible but
+        * not larger then pages that MMU driver that called us
+        * supports (ie. the ones provided by page_sizes).  This makes
+        * it possible to map the region using fewest possible number
+        * of entries.
+        */
+       pages = size >> PAGE_SHIFT;
+       do {
+               while (!(pages >> *orders))
+                       ++orders;
+
+               last = vcm_phys_alloc_list_order(last, &pages, flags, *orders,
+                                                &total_parts, gfp);
+               if (!last)
+                       return 0;
+
+       } while (*orders++ && pages);
+
+       if (pages)
+               return 0;
+
+       return total_parts;
+}
+
+static void vcm_phys_free_parts(struct vcm_phys_part *parts, unsigned count)
+{
+       do {
+               __free_pages(parts->page, ffs(parts->size) - 1 - PAGE_SHIFT);
+       } while (++parts, --count);
+}
+
+static void vcm_phys_free(struct vcm_phys *phys)
+{
+       vcm_phys_free_parts(phys->parts, phys->count);
+       kfree(phys);
+}
+
+struct vcm_phys *__must_check
+__vcm_phys_alloc(resource_size_t size, unsigned flags,
+                const unsigned char *orders, gfp_t gfp)
+{
+       struct vcm_phys_list *lst, *n;
+       struct vcm_phys_part *out;
+       struct vcm_phys *phys;
+       unsigned count;
+
+       if (WARN_ON((size & (PAGE_SIZE - 1)) || !size || !orders))
+               return ERR_PTR(-EINVAL);
+
+       lst = kmalloc(sizeof *lst, GFP_KERNEL);
+       if (!lst)
+               return ERR_PTR(-ENOMEM);
+
+       lst->next = NULL;
+       lst->count = 0;
+
+       count = vcm_phys_alloc_list(lst, size, flags, orders, gfp);
+       if (!count)
+               goto error;
+
+       phys = kmalloc(sizeof *phys + count * sizeof *phys->parts, GFP_KERNEL);
+       if (!phys)
+               goto error;
+
+       phys->free  = vcm_phys_free;
+       phys->count = count;
+       phys->size  = size;
+
+       out = phys->parts;
+       do {
+               memcpy(out, lst->parts, lst->count * sizeof *out);
+               out += lst->count;
+
+               n = lst->next;
+               kfree(lst);
+               lst = n;
+       } while (lst);
+
+       return phys;
+
+error:
+       do {
+               vcm_phys_free_parts(lst->parts, lst->count);
+
+               n = lst->next;
+               kfree(lst);
+               lst = n;
+       } while (lst);
+
+       return ERR_PTR(-ENOMEM);
+}
+EXPORT_SYMBOL_GPL(__vcm_phys_alloc);
+
+static inline bool is_of_order(dma_addr_t size, unsigned order)
+{
+       return !(size & (((dma_addr_t)PAGE_SIZE << order) - 1));
+}
+
+static int
+__vcm_phys_walk_part(dma_addr_t vaddr, const struct vcm_phys_part *part,
+                    const unsigned char *orders,
+                    int (*callback)(dma_addr_t vaddr, dma_addr_t paddr,
+                                    unsigned order, void *priv), void *priv,
+                    unsigned *limit)
+{
+       resource_size_t size = part->size;
+       dma_addr_t paddr = part->start;
+       resource_size_t ps;
+
+       while (!is_of_order(vaddr, *orders))
+               ++orders;
+       while (!is_of_order(paddr, *orders))
+               ++orders;
+
+       ps = PAGE_SIZE << *orders;
+       for (; *limit && size; --*limit) {
+               int ret;
+
+               while (ps > size)
+                       ps = PAGE_SIZE << *++orders;
+
+               ret = callback(vaddr, paddr, *orders, priv);
+               if (ret < 0)
+                       return ret;
+
+               ps = PAGE_SIZE << *orders;
+               vaddr += ps;
+               paddr += ps;
+               size  -= ps;
+       }
+
+       return 0;
+}
+
+int vcm_phys_walk(dma_addr_t _vaddr, const struct vcm_phys *phys,
+                 const unsigned char *orders,
+                 int (*callback)(dma_addr_t vaddr, dma_addr_t paddr,
+                                 unsigned order, void *arg),
+                 int (*recovery)(dma_addr_t vaddr, dma_addr_t paddr,
+                                 unsigned order, void *arg),
+                 void *priv)
+{
+       unsigned limit = ~0;
+       int r = 0;
+
+       if (WARN_ON(!phys || ((_vaddr | phys->size) & (PAGE_SIZE - 1)) ||
+                   !phys->size || !orders || !callback))
+               return -EINVAL;
+
+       for (;;) {
+               const struct vcm_phys_part *part = phys->parts;
+               unsigned count = phys->count;
+               dma_addr_t vaddr = _vaddr;
+               int ret = 0;
+
+               for (; count && limit; --count, ++part) {
+                       ret = __vcm_phys_walk_part(vaddr, part, orders,
+                                                  callback, priv, &limit);
+                       if (ret)
+                               break;
+
+                       vaddr += part->size;
+               }
+
+               if (r)
+                       /* We passed error recovery */
+                       return r;
+
+               /*
+                * Either operation suceeded or we were not provided
+                * with a recovery callback -- return.
+                */
+               if (!ret || !recovery)
+                       return ret;
+
+               /* Switch to recovery */
+               limit = ~0 - limit;
+               callback = recovery;
+               r = ret;
+       }
+}
+EXPORT_SYMBOL_GPL(vcm_phys_walk);
+
+#endif
-- 
1.6.2.5

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to