Skip to contentSkip to conversation

mm: iommu: An API to unify IOMMU, CPU and device memory management

RFC New Zach Pfeffer 1 participant 3 messages Jul 6, 2010

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

RFC v3 1/3 mm: iommu: An API to unify IOMMU, CPU and device memory management

This patch contains the documentation for the API, termed the Virtual Contiguous Memory Manager. Its use would allow all of the IOMMU to VM, VM to device and device to IOMMU interoperation code to be refactored into platform independent code.

Comments, suggestions and criticisms are welcome and wanted.

---
 Documentation/vcm.txt |  587 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 587 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/vcm.txt
Documentation/vcm.txt new file +587 -0
new file mode 100644
index 0000000..1c6a8be
@@ -0,0 +1,587 @@
+What is this document about?
+============================
+
+This document covers how to use the Virtual Contiguous Memory Manager
+(VCMM), how the first implementation works with a specific low-level
+Input/Output Memory Management Unit (IOMMU) and the way the VCMM is used
+from user-space. It also contains a section that describes why something
+like the VCMM is needed in the kernel.
+
+If anything in this document is wrong, please send patches to the
+maintainer of this file, listed at the bottom of the document.
+
+
+The Virtual Contiguous Memory Manager
+=====================================
+
+The VCMM was built to solve the system-wide memory mapping issues that
+occur when many bus-masters have IOMMUs.
+
+An IOMMU maps device addresses to physical addresses. It also insulates
+the system from spurious or malicious device bus transactions and allows
+fine-grained mapping attribute control. The Linux kernel core does not
+contain a generic API to handle IOMMU mapped memory; device driver writers
+must implement device specific code to interoperate with the Linux kernel
+core. As the number of IOMMUs increases, coordinating the many address
+spaces mapped by all discrete IOMMUs becomes difficult without in-kernel
+support.
+
+The VCMM API enables device independent IOMMU control, virtual memory
+manager (VMM) interoperation and non-IOMMU enabled device interoperation
+by treating devices with or without IOMMUs and all CPUs with or without
+MMUs, their mapping contexts and their mappings using common
+abstractions. Physical hardware is given a generic device type and mapping
+contexts are abstracted into Virtual Contiguous Memory (VCM)
+regions. Users "reserve" memory from VCMs and "back" their reservations
+with physical memory.
+
+Why the VCMM is Needed
+----------------------
+
+Driver writers who control devices with IOMMUs must contend with device
+control and memory management. Driver writers have a large device driver
+API that they can leverage to control their devices, but they are lacking
+a unified API to help them program mappings into IOMMUs and share those
+mappings with other devices and CPUs in the system.
+
+Sharing is complicated by Linux's CPU-centric VMM. The CPU-centric model
+generally makes sense because average hardware only contains a MMU for the
+CPU and possibly a graphics MMU. If every device in the system has one or
+more MMUs the CPU-centric memory management (MM) programming model breaks
+down.
+
+Abstracting IOMMU device programming into a common API has already begun
+in the Linux kernel. It was built to abstract the difference between AMD
+and Intel IOMMUs to support x86 virtualization on both platforms. The
+interface is listed in include/linux/iommu.h. It contains
+interfaces for mapping and unmapping as well as domain management. This
+interface has not gained widespread use outside the x86; PA-RISC, Alpha
+and SPARC architectures and ARM and PowerPC platforms all use their own
+mapping modules to control their IOMMUs. The VCMM contains an IOMMU
+programming layer, but since its abstraction supports map management
+independent of device control, the layer is not used directly. This
+higher-level view enables a new kernel service, not just an IOMMU
+interoperation layer.
+
+The General Idea: Map Management using Graphs
+---------------------------------------------
+
+Looking at mapping from a system-wide perspective reveals a general graph
+problem. The VCMM's API is built to manage the general mapping graph. Each
+node that talks to memory, either through an MMU or directly (physically
+mapped) can be thought of as the device-end of a mapping edge. The other
+edge is the physical memory (or intermediate virtual space) that is
+mapped.
+
+In the direct-mapped case the device is assigned a one-to-one MMU. This
+scheme allows direct mapped devices to participate in general graph
+management.
+
+The CPU nodes can also be brought under the same mapping abstraction with
+the use of a light overlay on the existing VMM. This light overlay allows
+VMM-managed mappings to interoperate with the common API. The light
+overlay enables this without substantial modifications to the existing
+VMM.
+
+In addition to CPU nodes that are running Linux (and the VMM), remote CPU
+nodes that may be running other operating systems can be brought into the
+general abstraction. Routing all memory management requests from a remote
+node through the central memory management framework enables new features
+like system-wide memory migration. This feature may only be feasible for
+large buffers that are managed outside of the fast-path, but having remote
+allocation in a system enables features that are impossible to build
+without it.
+
+The fundamental objects that support graph-based map management are:
+
+1) Virtual Contiguous Memory Regions
+
+2) Reservations
+
+3) Associated Virtual Contiguous Memory Regions
+
+4) Memory Targets
+
+5) Physical Memory Allocations
+
+Usage Overview
+--------------
+
+In a nutshell, users allocate Virtual Contiguous Memory Regions and
+associate those regions with one or more devices by creating an Associated
+Virtual Contiguous Memory Region. Users then create Reservations from the
+Virtual Contiguous Memory Region. At this point no physical memory has
+been committed to the reservation. To associate physical memory with a
+reservation a Physical Memory Allocation is created and the Reservation is
+backed with this allocation.
+
+include/linux/vcm.h includes comments documenting each API.
+
+Virtual Contiguous Memory Regions
+---------------------------------
+
+A Virtual Contiguous Memory Region (VCM) abstracts the memory space a
+device sees. The addresses of the region are only used by the devices
+which are associated with the region. This address space would normally be
+implemented as a device page table.
+
+A VCM is created and destroyed with three functions:
+
+    struct vcm *vcm_create(unsigned long start_addr, unsigned long len);
+
+    struct vcm *vcm_create_from_prebuilt(size_t ext_vcm_id);
+
+    int vcm_free(struct vcm *vcm);
+
+start_addr is an offset into the address space where allocations will
+start from. len is the length from start_addr of the VCM. Both functions
+generate an instance of a VCM.
+
+ext_vcm_id is used to pass a request to the VMM to generate a VCM
+instance. In the current implementation the call simply makes a note that
+the VCM instance is a VMM VCM instance for other interfaces usage. This
+muxing is seen throughout the implementation.
+
+vcm_create() and vcm_create_from_prebuilt() produce VCM instances for
+virtually mapped devices (IOMMUs and CPUs). To create a one-to-one mapped
+VCM, users pass the start_addr and len of the physical region. The VCMM
+matches this and records that the VCM instance is a one-to-one VCM.
+
+The newly created VCM instance can be passed to any function that needs to
+operate on or with a virtual contiguous memory region. Its main attributes
+are a start_addr and a len as well as an internal setting that allows the
+implementation to mux between true virtual spaces, one-to-one mapped
+spaces and VMM managed spaces.
+
+The current implementation uses the genalloc library to manage the VCM for
+IOMMU devices. Return values and more in-depth per-function documentation
+for these and the ones listed below are in include/linux/vcm.h.
+
+Reservations
+------------
+
+A Reservation is a contiguous region allocated from a VCM. There is no
+physical memory associated with it.
+
+A Reservation is created and destroyed with:
+
+    struct res *vcm_reserve(struct vcm *vcm, size_t len, u32 attr);
+
+    int vcm_unreserve(struct res *res);
+
+A vcm is a VCM created above. len is the length of the request. It can be
+up to the length of the VCM region the reservation is being created
+from. attr are mapping attributes: read, write, execute, user, supervisor,
+secure, not-cached, write-back/write-allocate, write-back/no
+write-allocate, write-through. These attrs are appropriate for ARM but can
+be changed to match to any architecture.
+
+The implementation calls gen_pool_alloc() for IOMMU devices,
+alloc_vm_area() for VMM areas and is a pass-through for one-to-one mapped
+areas.
+
+Associated Virtual Contiguous Memory Regions and Activation
+-----------------------------------------------------------
+
+An Associated Virtual Contiguous Memory Region (AVCM) is a mapping of a
+VCM to a device. The mapping can be active or inactive.
+
+An AVCM is managed with:
+
+    struct avcm *vcm_assoc(struct vcm *vcm, struct device *dev, u32 attr);
+
+    int vcm_deassoc(struct avcm *avcm);
+
+    int vcm_activate(struct avcm *avcm);
+
+    int vcm_deactivate(struct avcm *avcm);
+
+A VCM instance is a VCM created above. A dev is an opaque device handle
+thats passed down to the device driver the VCMM muxes in to handle a
+request. attr are association attributes: split, use-high or
+use-low. split controls which transactions hit a high-address page-table
+and which transactions hit a low-address page-table. For instance, all
+transactions whose most significant address bit is one would use the
+high-address page-table, any other transaction would use the low address
+page-table. This scheme is ARM-specific and could be changed in other
+architectures. One VCM instance can be associated with many devices and
+many VCM instances can be associated with one device.
+
+An AVCM is only a link. To program and deprogram a device with a VCM the
+user calls vcm_activate() and vcm_deactivate(). For IOMMU devices,
+activating a mapping programs the base address of a page table into an
+IOMMU. For VMM and one-to-one based devices, mappings are active
+immediately but the API does require an activation call for them for
+internal reference counting.
+
+Memory Targets
+--------------
+
+A Memory Target is a platform independent way of specifying a physical
+pool; it abstracts a pool of physical memory. The physical memory pool may
+be physically discontiguous, need to be allocated from in a unique way or
+have other user-defined attributes.
+
+Physical Memory Allocation and Reservation Backing
+--------------------------------------------------
+
+Physical memory is allocated as a separate step from reserving
+memory. This allows multiple reservations to back the same physical
+memory.
+
+A Physical Memory Allocation is managed using the following functions:
+
+    struct physmem *vcm_phys_alloc(enum memtype_t memtype,
+                                   size_t len, u32 attr);
+
+    int vcm_phys_free(struct physmem *physmem);
+
+    int vcm_back(struct res *res, struct physmem *physmem);
+
+    int vcm_unback(struct res *res);
+
+attr can include an alignment request, a specification to map memory using
+various block sizes and/or to use physically contiguous memory. memtype is
+one of the memory types listed in Memory Targets.
+
+The current implementation manages two pools of memory. One pool is a
+contiguous block of memory and the other is a set of contiguous block
+pools. In the current implementation the block pools contain 4K, 64K and
+1M blocks. The physical allocator does not try to split blocks from the
+contiguous block pools to satisfy requests.
+
+The use of 4K, 64K and 1M blocks solves a problem with some IOMMU
+hardware. IOMMUs are placed in front of multimedia engines to provide a
+contiguous address space to the device. Multimedia devices need large
+buffers and large buffers may map to a large number of physical
+blocks. IOMMUs tend to have small translation lookaside buffers
+(TLBs). Since the TLB is small the number of physical blocks that map a
+given range needs to be small or else the IOMMU will continually fetch new
+translations during a typical streamed multimedia flow. By using a 1 MB
+mapping (or 64K mapping) instead of a 4K mapping the number of misses can
+be minimized, allowing the multimedia block to meet its performance goals.
+
+Low Level Control
+-----------------
+
+It is necessary in some instances to access attributes and provide
+higher-level control of the low-level hardware abstraction. The API
+contains many members and functions for this task but the two that are
+typically used are:
+
+    res->dev_addr;
+
+    int vcm_hook(struct device *dev, vcm_handler handler, void *data);
+
+res->dev_addr is the device address given a reservation. This device
+address is a virtual IOMMU address for reservations on IOMMU VCMs, a
+virtual VMM address for reservations on VMM VCMs and a virtual (really
+physical since its one-to-one mapped) address for one-to-one devices.
+
+The function, vcm_hook, allows a caller in the kernel to register a
+user_handler. The handler is passed the data member passed to vcm_hook
+during a fault. The user can return 1 to indicate that the underlying
+driver should handle the fault and retry the transaction or they can
+return 0 to halt the transaction. If the user doesn't register a
+handler the low-level driver will print a warning and terminate the
+transaction.
+
+A Detailed Walk Through
+-----------------------
+
+The following call sequence walks through a typical allocation
+sequence. In the first stage the memory for a device is reserved and
+backed. This occurs without mapping the memory into a VMM VCM region. The
+second stage maps the first VCM region into a VMM VCM region so the kernel
+can read or write it. The second stage is not necessary if the VMM does
+not need to read or modify the contents of the original mapping.
+
+    Stage 1: Map and Allocate Memory for a Device
+
+    The call sequence starts by creating a VCM region:
+
+        vcm = vcm_create(start_addr, len);
+
+    The next call associates a VCM region with a device:
+
+        avcm = vcm_assoc(vcm, dev, attr);
+
+    To activate the association, users call vcm_activate() on the avcm from
+    the associate call. This programs the underlining device with the
+    mappings.
+
+        ret = vcm_activate(avcm);
+
+    Once a VCM region is created and associated it can be reserved from
+    with:
+
+        res = vcm_reserve(vcm, res_len, res_attr);
+
+    A user then allocates physical memory with:
+
+        physmem = vcm_phys_alloc(memtype, len, phys_attr);
+
+    To back the reservation with the physical memory allocation the user
+    calls:
+
+        vcm_back(res, physmem);
+
+
+    Stage 2: Map the Device's Memory into the VMM's VCM region
+
+    If the VMM needs to read and/or write the region that was just created,
+    the following calls are made.
+
+    The first call creates a prebuilt VCM with:
+
+        vcm_vmm = vcm_from_prebuilt(ext_vcm_id);
+
+    The prebuilt VCM is associated with the CPU device and activated with:
+
+        avcm_vmm = vcm_assoc(vcm_vmm, dev_cpu, attr);
+        vcm_activate(avcm_vmm);
+
+    A reservation is made on the VMM VCM with:
+
+        res_vmm = vcm_reserve(vcm_vmm, res_len, attr);
+
+    Finally, once the topology has been set up a vcm_back() allows the VMM
+    to read the memory using the physmem generated in stage 1:
+
+        vcm_back(res_vmm, physmem);
+
+Mapping IOMMU, one-to-one and VMM Reservations
+----------------------------------------------
+
+The following example demonstrates mapping IOMMU, one-to-one and VMM
+reservations to the same physical memory. It shows the use of phys_addr
+and phys_size to create a contiguous VCM for one-to-one mapped devices.
+
+    The user allocates physical memory:
+
+        physmem = vcm_phys_alloc(memtype, SZ_2MB + SZ_4K, CONTIGUOUS);
+
+    Creates an IOMMU VCM:
+
+        vcm_iommu = vcm_create(SZ_1K, SZ_16M);
+
+    Creates a one-to-one VCM:
+
+        vcm_onetoone = vcm_create(phys_addr, phys_size);
+
+    Creates a Prebuit VCM:
+
+        vcm_vmm = vcm_from_prebuit(ext_vcm_id);
+
+    Associate and activate all three to their respective devices:
+
+        avcm_iommu = vcm_assoc(vcm_iommu, dev_iommu, attr0);
+        avcm_onetoone = vcm_assoc(vcm_onetoone, dev_onetoone, attr1);
+        avcm_vmm = vcm_assoc(vcm_vmm, dev_cpu, attr2);
+        vcm_activate(avcm_iommu);
+        vcm_activate(avcm_onetoone);
+        vcm_activate(avcm_vmm);
+
+    Associations that fail return 0.
+
+    And finally, creates and backs reservations on all 3 such that they
+    all point to the same memory:
+
+        res_iommu = vcm_reserve(vcm_iommu, SZ_2MB + SZ_4K, attr);
+        res_onetoone = vcm_reserve(vcm_onetoone, SZ_2MB + SZ_4K, attr);
+        res_vmm = vcm_reserve(vcm_vmm, SZ_2MB + SZ_4K, attr);
+        vcm_back(res_iommu, physmem);
+        vcm_back(res_onetoone, physmem);
+        vcm_back(res_vmm, physmem);
+
+    Like associations, reservations that fail return 0.
+
+VCM Summary
+-----------
+
+The VCMM is an attempt to abstract attributes of three distinct classes of
+mappings into one API. The VCMM allows users to reason about mappings as
+first class objects. It also allows memory mappings to flow from the
+traditional 4K mappings prevalent on systems today to more efficient block
+sizes. Finally, it allows users to manage mapping interoperation without
+becoming VMM experts. These features will allow future systems with many
+MMU mapped devices to interoperate simply and therefore correctly.
+
+
+IOMMU Hardware Control
+======================
+
+The VCM currently supports a single type of IOMMU, a Qualcomm System MMU
+(SMMU). The SMMU interface contains functions to map and unmap virtual
+addresses, perform address translations and initialize hardware. A
+Qualcomm SMMU can contain multiple MMU contexts. Each context can
+translate in parallel. All contexts in a SMMU share one global translation
+look-aside buffer (TLB).
+
+To support context muxing the SMMU module creates and manages device
+independent virtual contexts. These context abstractions are bound to
+actual contexts at run-time. Once bound, a context can be activated. This
+activation programs the underlying context with the virtual context
+affecting a context switch.
+
+The following functions are all documented in:
+
+    arch/arm/mach-msm/include/mach/smmu_driver.h.
+
+Mapping
+-------
+
+To map and unmap a virtual page into physical space the VCM calls:
+
+    int smmu_map(struct smmu_dev *dev, unsigned long pa,
+                 unsigned long va, unsigned long len, unsigned int attr);
+
+    int smmu_unmap(struct smmu_dev *dev, unsigned long va,
+                   unsigned long len);
+
+    int smmu_update_start(struct smmu_dev *dev);
+
+    int smmu_update_done(struct smmu_dev *dev);
+
+The size given to map must be 4K, 64K, 1M or 16M and the VA and PA must be
+aligned to the given size. smmu_update_start() and smmu_update_done()
+should be called before and after each map or unmap.
+
+Translation
+-----------
+
+To request a hardware VA to PA translation on a single address the VCM
+calls:
+
+    unsigned long smmu_translate(struct smmu_dev *dev,
+                                 unsigned long va);
+
+Fault Handling
+--------------
+
+To register an interrupt handler for a context the VCM calls:
+
+    int smmu_hook_interrupt(struct smmu_dev *dev, vcm_handler handler,
+                            void *data);
+
+The registered interrupt handler should return 1 if it wants the SMMU
+driver to retry the transaction again and 0 if it wants the SMMU driver to
+terminate the transaction.
+
+Managing SMMU Initialization and Contexts
+-----------------------------------------
+
+SMMU hardware initialization and management happens in 2 steps. The first
+step initializes global SMMU devices and abstract device contexts. The
+second step binds contexts and devices.
+
+An SMMU hardware instance is built with:
+
+    int smmu_drvdata_init(struct smmu_driver *drv, unsigned long base,
+                          int irq);
+
+An SMMU context is initialized and deinitialized with:
+
+    struct smmu_dev *smmu_ctx_init(int ctx);
+    int smmu_ctx_deinit(struct smmu_dev *dev);
+
+An abstract SMMU context is bound to a particular SMMU with:
+
+    int smmu_ctx_bind(struct smmu_dev *ctx, struct smmu_driver *drv);
+
+Activation
+----------
+
+Activation affects a context switch.
+
+Activation, deactivation and activation state testing are done with:
+
+    int smmu_activate(struct smmu_dev *dev);
+    int smmu_deactivate(struct smmu_dev *dev);
+    int smmu_is_active(struct smmu_dev *dev);
+
+
+Userspace Access to Devices with IOMMUs
+=======================================
+
+A device that issues transactions through an IOMMU must work with two
+APIs. The first API is the VCM. The VCM API is device independent. Users
+pass the VCM a dev_id and the VCM makes calls on the hardware device it
+has been configured with using this dev_id. The second API is whatever
+device topology has been created to organize the particular IOMMUs in a
+system. The only constraint on this second API is that it must give the
+user a single dev_id that it can pass through the VCM.
+
+For the Qualcomm SMMUs the second API consists of a tree of platform
+devices and two platform drivers as well as a context lookup function that
+traverses the device tree and returns a dev_id given a context name.
+
+Qualcomm SMMU Device Tree
+-------------------------
+
+The current tree organizes the devices into a tree that looks like the
+following:
+
+smmu/
+               smmu0/
+                                ctx0
+                                ctx1
+                                ctx2
+               smmu1/
+                                ctx3
+
+
+Each context, ctx[n] and each smmu, smmu[n] is given a name. Since users
+are interested in contexts not smmus, the context name is passed to a
+function to find the dev_id associated with that name. The functions to
+find, free and get the base address (since the device probe function calls
+ioremap to map the SMMUs configuration registers into the kernel) are
+listed here:
+
+    struct smmu_dev *smmu_get_ctx_instance(char *ctx_name);
+    int smmu_free_ctx_instance(struct smmu_dev *dev);
+    unsigned long smmu_get_base_addr(struct smmu_dev *dev);
+
+Documentation for these functions is in:
+
+    arch/arm/mach-msm/include/mach/smmu_device.h
+
+Each context is given a dev node named after the context. For example:
+
+    /dev/vcodec_a_mm1
+    /dev/vcodec_b_mm2
+    /dev/vcodec_stream
+    etc...
+
+Users open, close and mmap these nodes to access VCM buffers from
+userspace in the same way that they used to open, close and mmap /dev
+nodes that represented large physically contiguous buffers (called PMEM
+buffers on Android).
+
+Example
+-------
+
+An abbreviated example is shown here:
+
+Users get the dev_id associated with their target context, create a VCM
+topology appropriate for their device and finally associate the VCMs of
+the topology with the contexts that will take the VCMs:
+
+    dev_id = smmu_get_ctx_instance(vcodec_a_stream);
+
+create vcm and needed topology
+
+    avcm = vcm_assoc(vcm, dev_id, attr);
+
+Tying it all Together
+---------------------
+
+VCMs, IOMMUs and the device tree all work to support system-wide memory
+mappings. The use of each API in this system allows users to concentrate
+on the relevant details without needing to worry about low-level
+details. The API's clear separation of memory spaces and the devices that
+support those memory spaces continues the Linux tradition of abstracting the
+what from the how.
+
+
+Maintainer: Zach Pfeffer <[email protected]>
Signature · 5 lines
1.7.0.2

--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
#1 lore raw

RFC 2/3 mm: iommu: A physical allocator for the VCMM

The Virtual Contiguous Memory Manager (VCMM) needs a physical pool to allocate from. It breaks up the pool into sub-pools of same-sized chunks. In particular, it breaks the pool it manages into sub-pools of 1 MB, 64 KB and 4 KB chunks.

When a user makes a request, this allocator satisfies that request from the sub-pools using a "maximum-munch" strategy. This strategy attempts to satisfy a request using the largest chunk-size without over-allocating, then moving on to the next smallest size without over-allocating and finally completing the request with the smallest sized chunk, over-allocating if necessary.

The maximum-munch strategy allows physical page allocation for small TLBs that need to map a given range using the minimum number of mappings.

Although the allocator has been configured for 1 MB, 64 KB and 4 KB chunks, it can be easily extended to other chunk sizes.

---
 arch/arm/mm/vcm_alloc.c   |  425 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/vcm_alloc.h |   70 ++++++++
 2 files changed, 495 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mm/vcm_alloc.c
 create mode 100644 include/linux/vcm_alloc.h
arch/arm/mm/vcm_alloc.c new file +425 -0
new file mode 100644
index 0000000..e592e71
@@ -0,0 +1,425 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/vcm_alloc.h>
+#include <linux/string.h>
+#include <asm/sizes.h>
+
+/* Amount of memory managed by VCM */
+#define TOTAL_MEM_SIZE SZ_32M
+
+static unsigned int base_pa = 0x80000000;
+int basicalloc_init;
+
+int chunk_sizes[NUM_CHUNK_SIZES] = {SZ_1M, SZ_64K, SZ_4K};
+int init_num_chunks[] = {
+	(TOTAL_MEM_SIZE/2) / SZ_1M,
+	(TOTAL_MEM_SIZE/4) / SZ_64K,
+	(TOTAL_MEM_SIZE/4) / SZ_4K
+};
+#define LAST_SZ() (ARRAY_SIZE(chunk_sizes) - 1)
+
+#define vcm_alloc_err(a, ...)						\
+	pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__)
+
+struct phys_chunk_head {
+	struct list_head head;
+	int num;
+};
+
+struct phys_mem {
+	struct phys_chunk_head heads[ARRAY_SIZE(chunk_sizes)];
+} phys_mem;
+
+static int is_allocated(struct list_head *allocated)
+{
+	/* This should not happen under normal conditions */
+	if (!allocated) {
+		vcm_alloc_err("no allocated\n");
+		return 0;
+	}
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return 0;
+	}
+	return !list_empty(allocated);
+}
+
+static int count_allocated_size(enum chunk_size_idx idx)
+{
+	int cnt = 0;
+	struct phys_chunk *chunk, *tmp;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return 0;
+	}
+
+	list_for_each_entry_safe(chunk, tmp,
+				 &phys_mem.heads[idx].head, list) {
+		if (is_allocated(&chunk->allocated))
+			cnt++;
+	}
+
+	return cnt;
+}
+
+
+int vcm_alloc_get_mem_size(void)
+{
+	return TOTAL_MEM_SIZE;
+}
+EXPORT_SYMBOL(vcm_alloc_get_mem_size);
+
+
+int vcm_alloc_blocks_avail(enum chunk_size_idx idx)
+{
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return 0;
+	}
+
+	return phys_mem.heads[idx].num;
+}
+EXPORT_SYMBOL(vcm_alloc_blocks_avail);
+
+
+int vcm_alloc_get_num_chunks(void)
+{
+	return ARRAY_SIZE(chunk_sizes);
+}
+EXPORT_SYMBOL(vcm_alloc_get_num_chunks);
+
+
+int vcm_alloc_all_blocks_avail(void)
+{
+	int i;
+	int cnt = 0;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i)
+		cnt += vcm_alloc_blocks_avail(i);
+	return cnt;
+}
+EXPORT_SYMBOL(vcm_alloc_all_blocks_avail);
+
+
+int vcm_alloc_count_allocated(void)
+{
+	int i;
+	int cnt = 0;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i)
+		cnt += count_allocated_size(i);
+	return cnt;
+}
+EXPORT_SYMBOL(vcm_alloc_count_allocated);
+
+
+void vcm_alloc_print_list(int just_allocated)
+{
+	int i;
+	struct phys_chunk *chunk, *tmp;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i) {
+		if (list_empty(&phys_mem.heads[i].head))
+			continue;
+		list_for_each_entry_safe(chunk, tmp,
+					 &phys_mem.heads[i].head, list) {
+			if (just_allocated && !is_allocated(&chunk->allocated))
+				continue;
+
+			printk(KERN_INFO "pa = %#x, size = %#x\n",
+			       chunk->pa, chunk_sizes[chunk->size_idx]);
+		}
+	}
+}
+EXPORT_SYMBOL(vcm_alloc_print_list);
+
+
+int vcm_alloc_idx_to_size(int idx)
+{
+	return chunk_sizes[idx];
+}
+EXPORT_SYMBOL(vcm_alloc_idx_to_size);
+
+
+int vcm_alloc_destroy(void)
+{
+	int i;
+	struct phys_chunk *chunk, *tmp;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		return -1;
+	}
+
+	/* can't destroy a space that has allocations */
+	if (vcm_alloc_count_allocated()) {
+		vcm_alloc_err("allocations still present\n");
+		return -1;
+	}
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i) {
+
+		if (list_empty(&phys_mem.heads[i].head))
+			continue;
+		list_for_each_entry_safe(chunk, tmp,
+					 &phys_mem.heads[i].head, list) {
+			list_del(&chunk->list);
+			memset(chunk, 0, sizeof(*chunk));
+			kfree(chunk);
+		}
+	}
+
+	basicalloc_init = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(vcm_alloc_destroy);
+
+
+int vcm_alloc_init(unsigned int set_base_pa)
+{
+	int i = 0, j = 0;
+	struct phys_chunk *chunk;
+	int pa;
+
+	if (set_base_pa)
+		base_pa = set_base_pa;
+
+	pa = base_pa;
+
+	/* no double inits */
+	if (basicalloc_init) {
+		vcm_alloc_err("double basicalloc_init\n");
+		BUG();
+		return -1;
+	}
+
+	/* separate out to ensure good cleanup */
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i) {
+		INIT_LIST_HEAD(&phys_mem.heads[i].head);
+		phys_mem.heads[i].num = 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i) {
+		for (j = 0; j < init_num_chunks[i]; ++j) {
+			chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+			if (!chunk) {
+				vcm_alloc_err("null chunk\n");
+				goto fail;
+			}
+			chunk->pa = pa; pa += chunk_sizes[i];
+			chunk->size_idx = i;
+			INIT_LIST_HEAD(&chunk->allocated);
+			list_add_tail(&chunk->list, &phys_mem.heads[i].head);
+			phys_mem.heads[i].num++;
+		}
+	}
+
+	basicalloc_init = 1;
+	return 0;
+fail:
+	vcm_alloc_destroy();
+	return -1;
+}
+EXPORT_SYMBOL(vcm_alloc_init);
+
+
+int vcm_alloc_free_blocks(struct phys_chunk *alloc_head)
+{
+	struct phys_chunk *chunk, *tmp;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		goto fail;
+	}
+
+	if (!alloc_head) {
+		vcm_alloc_err("no alloc_head\n");
+		goto fail;
+	}
+
+	list_for_each_entry_safe(chunk, tmp, &alloc_head->allocated,
+				 allocated) {
+		list_del_init(&chunk->allocated);
+		phys_mem.heads[chunk->size_idx].num++;
+	}
+
+	return 0;
+fail:
+	return -1;
+}
+EXPORT_SYMBOL(vcm_alloc_free_blocks);
+
+
+int vcm_alloc_num_blocks(int num,
+			 enum chunk_size_idx idx, /* chunk size */
+			 struct phys_chunk *alloc_head)
+{
+	struct phys_chunk *chunk;
+	int num_allocated = 0;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("no basicalloc_init\n");
+		goto fail;
+	}
+
+	if (!alloc_head) {
+		vcm_alloc_err("no alloc_head\n");
+		goto fail;
+	}
+
+	if (list_empty(&phys_mem.heads[idx].head)) {
+		vcm_alloc_err("list is empty\n");
+		goto fail;
+	}
+
+	if (vcm_alloc_blocks_avail(idx) < num) {
+		vcm_alloc_err("not enough blocks? num=%d\n", num);
+		goto fail;
+	}
+
+	list_for_each_entry(chunk, &phys_mem.heads[idx].head, list) {
+		if (num_allocated == num)
+			break;
+		if (is_allocated(&chunk->allocated))
+			continue;
+
+		list_add_tail(&chunk->allocated, &alloc_head->allocated);
+		phys_mem.heads[idx].num--;
+		num_allocated++;
+	}
+	return num_allocated;
+fail:
+	return 0;
+}
+EXPORT_SYMBOL(vcm_alloc_num_blocks);
+
+
+int vcm_alloc_max_munch(int len,
+			struct phys_chunk *alloc_head)
+{
+	int i;
+
+	int blocks_req = 0;
+	int block_residual = 0;
+	int blocks_allocated = 0;
+
+	int ba = 0;
+
+	if (!basicalloc_init) {
+		vcm_alloc_err("basicalloc_init is 0\n");
+		goto fail;
+	}
+
+	if (!alloc_head) {
+		vcm_alloc_err("alloc_head is NULL\n");
+		goto fail;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(chunk_sizes); ++i) {
+		blocks_req = len / chunk_sizes[i];
+		block_residual = len % chunk_sizes[i];
+
+		len = block_residual; /* len left */
+		if (blocks_req) {
+			int blocks_available = 0;
+			int blocks_diff = 0;
+			int bytes_diff = 0;
+
+			blocks_available = vcm_alloc_blocks_avail(i);
+			if (blocks_available < blocks_req) {
+				blocks_diff =
+					(blocks_req - blocks_available);
+				bytes_diff =
+					blocks_diff * chunk_sizes[i];
+
+				/* add back in the rest */
+				len += bytes_diff;
+			} else {
+				/* got all the blocks I need */
+				blocks_available =
+					(blocks_available > blocks_req)
+					? blocks_req : blocks_available;
+			}
+
+			ba = vcm_alloc_num_blocks(blocks_available, i,
+						  alloc_head);
+
+			if (ba != blocks_available) {
+				vcm_alloc_err("blocks allocated (%i) !="
+					      " blocks_available (%i):"
+					      " chunk size = %#x,"
+					      " alloc_head = %p\n",
+					      ba, blocks_available,
+					      i, (void *) alloc_head);
+				goto fail;
+			}
+			blocks_allocated += blocks_available;
+		}
+	}
+
+	if (len) {
+		int blocks_available = 0;
+
+		blocks_available = vcm_alloc_blocks_avail(LAST_SZ());
+
+		if (blocks_available > 1) {
+			ba = vcm_alloc_num_blocks(1, LAST_SZ(), alloc_head);
+			if (ba != 1) {
+				vcm_alloc_err("blocks allocated (%i) !="
+					      " blocks_available (%i):"
+					      " chunk size = %#x,"
+					      " alloc_head = %p\n",
+					      ba, 1,
+					      LAST_SZ(),
+					      (void *) alloc_head);
+				goto fail;
+			}
+			blocks_allocated += 1;
+		} else {
+			vcm_alloc_err("blocks_available (%#x) <= 1\n",
+				      blocks_available);
+			goto fail;
+		}
+	}
+
+	return blocks_allocated;
+fail:
+	vcm_alloc_free_blocks(alloc_head);
+	return 0;
+}
+EXPORT_SYMBOL(vcm_alloc_max_munch);
include/linux/vcm_alloc.h new file +70 -0

70 lines not on this page · raw message

Signature · 11 lines
1.7.0.2

--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#2 lore raw

RFC v3 3/3 mm: iommu: The Virtual Contiguous Memory Manager

In reply to Zach Pfeffer

The Virtual Contiguous Memory Manager (VCMM) allows the CPU, IOMMU devices and physically mapped devices to map the same physical memory using a common API. It achieves this by abstracting mapping into graph management.

Within the graph abstraction, the device end-points are CPUs with and without MMUs and devices with and without IOMMUs. The mapped memory is the other end-point. In the [IO]MMU case this is readily apparent. In the non-[IO]MMU case, it is as apparent if you give each device a "one-to-one" mapper. These mappings are wrapped up into "reservations" represented by struct res's.

Also part of this graph is a mapping of a virtual space, struct vcm, to a device. One virtual space may be associated with multiple devices and multiple virtual-spaces may be associated with the same device. In the case of a one-to-one device this virtual-space mirrors the physical-space. A virtual-space is tied to a device using a "association", a struct avcm. A device may be associated with a virtual-space without being programed to translate based on that virtual-space. Programming occurs using an activate call.

The physical side of the mapping (or intermediate address-space side in virtualized environments) is represented by a struct physmem. Due to the peculiar needs of various IOMMUs, the VCM contains a physical allocation subsystem that manages blocks of different sizes from different pools. This allows fine grained control of block and physical placement, a feature many advanced IOMMU devices need.

Once a user has made a reservation on a VCM and allocated physical memory, the two graph end-points are joined in a backing step. This step allows multiple reservations to map the same physical location.

Many of the functions in the API take various attributes that provide fine grained control of the objects they create. For instance, reservations can map cachable memory and physical allocations can be constrained to use a particular subset of block sizes.

---
 arch/arm/mm/vcm.c         | 1877 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/vcm.h       |  661 ++++++++++++++++
 include/linux/vcm_types.h |  338 ++++++++
 3 files changed, 2876 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mm/vcm.c
 create mode 100644 include/linux/vcm.h
 create mode 100644 include/linux/vcm_types.h
arch/arm/mm/vcm.c new file +1877 -0
new file mode 100644
index 0000000..2c951c3
@@ -0,0 +1,1877 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vcm_mm.h>
+#include <linux/vcm.h>
+#include <linux/vcm_types.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+
+#include <asm/page.h>
+#include <asm/sizes.h>
+
+#ifdef CONFIG_SMMU
+#include <mach/smmu_driver.h>
+#endif
+
+/* alloc_vm_area */
+#include <linux/pfn.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+
+/* may be temporary */
+#include <linux/bootmem.h>
+
+#include <asm/cacheflush.h>
+#include <asm/mach/map.h>
+
+#define BOOTMEM_SZ	SZ_32M
+#define BOOTMEM_ALIGN	SZ_1M
+
+#define CONT_SZ		SZ_8M
+#define CONT_ALIGN	SZ_1M
+
+#define ONE_TO_ONE_CHK 1
+
+#define vcm_err(a, ...)							\
+	pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__)
+
+static void *bootmem;
+static void *bootmem_cont;
+static struct vcm *cont_vcm;
+static struct phys_chunk *cont_phys_chunk;
+
+DEFINE_SPINLOCK(vcmlock);
+
+static int vcm_no_res(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	return list_empty(&vcm->res_head);
+fail:
+	return -EINVAL;
+}
+
+static int vcm_no_assoc(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	return list_empty(&vcm->assoc_head);
+fail:
+	return -EINVAL;
+}
+
+static int vcm_all_activated(struct vcm *vcm)
+{
+	struct avcm *avcm;
+
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	list_for_each_entry(avcm, &vcm->assoc_head, assoc_elm)
+		if (!avcm->is_active)
+			return 0;
+
+	return 1;
+fail:
+	return -1;
+}
+
+static void vcm_destroy_common(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		return;
+	}
+
+	memset(vcm, 0, sizeof(*vcm));
+	kfree(vcm);
+}
+
+static struct vcm *vcm_create_common(void)
+{
+	struct vcm *vcm = 0;
+
+	vcm = kzalloc(sizeof(*vcm), GFP_KERNEL);
+	if (!vcm) {
+		vcm_err("kzalloc(%i, GFP_KERNEL) ret 0\n",
+			sizeof(*vcm));
+		goto fail;
+	}
+
+	INIT_LIST_HEAD(&vcm->res_head);
+	INIT_LIST_HEAD(&vcm->assoc_head);
+
+	return vcm;
+
+fail:
+	return NULL;
+}
+
+
+static int vcm_create_pool(struct vcm *vcm, size_t start_addr, size_t len)
+{
+	int ret = 0;
+
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	vcm->start_addr = start_addr;
+	vcm->len = len;
+
+	vcm->pool = gen_pool_create(PAGE_SHIFT, -1);
+	if (!vcm->pool) {
+		vcm_err("gen_pool_create(%x, -1) ret 0\n", PAGE_SHIFT);
+		goto fail;
+	}
+
+	ret = gen_pool_add(vcm->pool, start_addr, len, -1);
+	if (ret) {
+		vcm_err("gen_pool_add(%p, %p, %i, -1) ret %i\n", vcm->pool,
+			(void *) start_addr, len, ret);
+		goto fail2;
+	}
+
+	return 0;
+
+fail2:
+	gen_pool_destroy(vcm->pool);
+fail:
+	return -1;
+}
+
+
+static struct vcm *vcm_create_flagged(int flag, size_t start_addr, size_t len)
+{
+	int ret = 0;
+	struct vcm *vcm = 0;
+
+	vcm = vcm_create_common();
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	/* special one-to-one mapping case */
+	if ((flag & ONE_TO_ONE_CHK) &&
+	    bootmem_cont &&
+	    __pa(bootmem_cont) &&
+	    start_addr == __pa(bootmem_cont) &&
+	    len == CONT_SZ) {
+		vcm->type = VCM_ONE_TO_ONE;
+	} else {
+		ret = vcm_create_pool(vcm, start_addr, len);
+		vcm->type = VCM_DEVICE;
+	}
+
+	if (ret) {
+		vcm_err("vcm_create_pool(%p, %p, %i) ret %i\n", vcm,
+			(void *) start_addr, len, ret);
+		goto fail2;
+	}
+
+	return vcm;
+
+fail2:
+	vcm_destroy_common(vcm);
+fail:
+	return NULL;
+}
+
+struct vcm *vcm_create(unsigned long start_addr, size_t len)
+{
+	unsigned long flags;
+	struct vcm *vcm;
+
+	spin_lock_irqsave(&vcmlock, flags);
+	vcm = vcm_create_flagged(ONE_TO_ONE_CHK, start_addr, len);
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return vcm;
+}
+
+
+static int ext_vcm_id_valid(size_t ext_vcm_id)
+{
+	return ((ext_vcm_id == VCM_PREBUILT_KERNEL) ||
+		(ext_vcm_id == VCM_PREBUILT_USER));
+}
+
+
+struct vcm *vcm_create_from_prebuilt(size_t ext_vcm_id)
+{
+	unsigned long flags;
+	struct vcm *vcm = 0;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!ext_vcm_id_valid(ext_vcm_id)) {
+		vcm_err("ext_vcm_id_valid(%i) ret 0\n", ext_vcm_id);
+		goto fail;
+	}
+
+	vcm = vcm_create_common();
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	if (ext_vcm_id == VCM_PREBUILT_KERNEL)
+		vcm->type = VCM_EXT_KERNEL;
+	else if (ext_vcm_id == VCM_PREBUILT_USER)
+		vcm->type = VCM_EXT_USER;
+	else {
+		vcm_err("UNREACHABLE ext_vcm_id is illegal\n");
+		goto fail_free;
+	}
+
+	/* TODO: set kernel and userspace start_addr and len, if this
+	 * makes sense */
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return vcm;
+
+fail_free:
+	vcm_destroy_common(vcm);
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return NULL;
+}
+
+
+struct vcm *vcm_clone(struct vcm *vcm)
+{
+	return 0;
+}
+
+
+/* No lock needed, vcm->start_addr is never updated after creation */
+size_t vcm_get_start_addr(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		return 1;
+	}
+
+	return vcm->start_addr;
+}
+
+
+/* No lock needed, vcm->len is never updated after creation */
+size_t vcm_get_len(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		return 0;
+	}
+
+	return vcm->len;
+}
+
+
+static int vcm_free_common_rule(struct vcm *vcm)
+{
+	int ret;
+
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	ret = vcm_no_res(vcm);
+	if (!ret) {
+		vcm_err("vcm_no_res(%p) ret 0\n", vcm);
+		goto fail_busy;
+	}
+
+	if (ret == -EINVAL) {
+		vcm_err("vcm_no_res(%p) ret -EINVAL\n", vcm);
+		goto fail;
+	}
+
+	ret = vcm_no_assoc(vcm);
+	if (!ret) {
+		vcm_err("vcm_no_assoc(%p) ret 0\n", vcm);
+		goto fail_busy;
+	}
+
+	if (ret == -EINVAL) {
+		vcm_err("vcm_no_assoc(%p) ret -EINVAL\n", vcm);
+		goto fail;
+	}
+
+	return 0;
+
+fail_busy:
+	return -EBUSY;
+fail:
+	return -EINVAL;
+}
+
+
+static int vcm_free_pool_rule(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	/* A vcm always has a valid pool, don't free the vcm because
+	   what we got is probably invalid.
+	*/
+	if (!vcm->pool) {
+		vcm_err("NULL vcm->pool\n");
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	return -EINVAL;
+}
+
+
+static void vcm_free_common(struct vcm *vcm)
+{
+	memset(vcm, 0, sizeof(*vcm));
+
+	kfree(vcm);
+}
+
+
+static int vcm_free_pool(struct vcm *vcm)
+{
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	gen_pool_destroy(vcm->pool);
+
+	return 0;
+
+fail:
+	return -1;
+}
+
+
+static int __vcm_free(struct vcm *vcm)
+{
+	int ret;
+
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	ret = vcm_free_common_rule(vcm);
+	if (ret != 0) {
+		vcm_err("vcm_free_common_rule(%p) ret %i\n", vcm, ret);
+		goto fail;
+	}
+
+	if (vcm->type == VCM_DEVICE) {
+		ret = vcm_free_pool_rule(vcm);
+		if (ret != 0) {
+			vcm_err("vcm_free_pool_rule(%p) ret %i\n",
+				(void *) vcm, ret);
+			goto fail;
+		}
+
+		ret = vcm_free_pool(vcm);
+		if (ret != 0) {
+			vcm_err("vcm_free_pool(%p) ret %i", (void *) vcm, ret);
+			goto fail;
+		}
+	}
+
+	vcm_free_common(vcm);
+
+	return 0;
+
+fail:
+	return -EINVAL;
+}
+
+int vcm_free(struct vcm *vcm)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&vcmlock, flags);
+	ret = __vcm_free(vcm);
+	spin_unlock_irqrestore(&vcmlock, flags);
+
+	return ret;
+}
+
+
+static struct res *__vcm_reserve(struct vcm *vcm, size_t len, u32 attr)
+{
+	struct res *res = NULL;
+
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	if (len == 0) {
+		vcm_err("len is 0\n");
+		goto fail;
+	}
+
+	res = kzalloc(sizeof(*res), GFP_KERNEL);
+	if (!res) {
+		vcm_err("kzalloc(%i, GFP_KERNEL) ret 0", sizeof(*res));
+		goto fail;
+	}
+
+	INIT_LIST_HEAD(&res->res_elm);
+	res->vcm = vcm;
+	res->len = len;
+	res->attr = attr;
+
+	if (len/SZ_1M)
+		res->alignment_req = SZ_1M;
+	else if (len/SZ_64K)
+		res->alignment_req = SZ_64K;
+	else
+		res->alignment_req = SZ_4K;
+
+	res->aligned_len = res->alignment_req + len;
+
+	switch (vcm->type) {
+	case VCM_DEVICE:
+		/* should always be not zero */
+		if (!vcm->pool) {
+			vcm_err("NULL vcm->pool\n");
+			goto fail2;
+		}
+
+		res->ptr = gen_pool_alloc(vcm->pool, res->aligned_len);
+		if (!res->ptr) {
+			vcm_err("gen_pool_alloc(%p, %i) ret 0\n",
+				vcm->pool, res->aligned_len);
+			goto fail2;
+		}
+
+		/* Calculate alignment... this will all change anyway */
+		res->dev_addr = res->ptr +
+			(res->alignment_req -
+			 (res->ptr & (res->alignment_req - 1)));
+
+		break;
+	case VCM_EXT_KERNEL:
+		res->vm_area = alloc_vm_area(res->aligned_len);
+		res->mapped = 0; /* be explicit */
+		if (!res->vm_area) {
+			vcm_err("NULL res->vm_area\n");
+			goto fail2;
+		}
+
+		res->dev_addr = (size_t) res->vm_area->addr +
+			(res->alignment_req -
+			 ((size_t) res->vm_area->addr &
+			  (res->alignment_req - 1)));
+
+		break;
+	case VCM_ONE_TO_ONE:
+		break;
+	default:
+		vcm_err("%i is an invalid vcm->type\n", vcm->type);
+		goto fail2;
+	}
+
+	list_add_tail(&res->res_elm, &vcm->res_head);
+
+	return res;
+
+fail2:
+	kfree(res);
+fail:
+	return 0;
+}
+
+
+struct res *vcm_reserve(struct vcm *vcm, size_t len, u32 attr)
+{
+	unsigned long flags;
+	struct res *res;
+
+	spin_lock_irqsave(&vcmlock, flags);
+	res = __vcm_reserve(vcm, len, attr);
+	spin_unlock_irqrestore(&vcmlock, flags);
+
+	return res;
+}
+
+
+struct res *vcm_reserve_at(enum memtarget_t memtarget, struct vcm* vcm,
+		     size_t len, u32 attr)
+{
+	return 0;
+}
+
+
+/* No lock needed, res->vcm is never updated after creation */
+struct vcm *vcm_get_vcm_from_res(struct res *res)
+{
+	if (!res) {
+		vcm_err("NULL res\n");
+		return 0;
+	}
+
+	return res->vcm;
+}
+
+
+static int __vcm_unreserve(struct res *res)
+{
+	struct vcm *vcm;
+
+	if (!res) {
+		vcm_err("NULL res\n");
+		goto fail;
+	}
+
+	if (!res->vcm) {
+		vcm_err("NULL res->vcm\n");
+		goto fail;
+	}
+
+	vcm = res->vcm;
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	switch (vcm->type) {
+	case VCM_DEVICE:
+		if (!res->vcm->pool) {
+			vcm_err("NULL (res->vcm))->pool\n");
+			goto fail;
+		}
+
+		/* res->ptr could be zero, this isn't an error */
+		gen_pool_free(res->vcm->pool, res->ptr,
+			      res->aligned_len);
+		break;
+	case VCM_EXT_KERNEL:
+		if (res->mapped) {
+			vcm_err("res->mapped is true\n");
+			goto fail;
+		}
+
+		/* This may take a little explaining.
+		 * In the kernel vunmap will free res->vm_area
+		 * so if we've called it then we shouldn't call
+		 * free_vm_area(). If we've called it we set
+		 * res->vm_area to 0.
+		 */
+		if (res->vm_area) {
+			free_vm_area(res->vm_area);
+			res->vm_area = 0;
+		}
+
+		break;
+	case VCM_ONE_TO_ONE:
+		break;
+	default:
+		vcm_err("%i is an invalid vcm->type\n", vcm->type);
+		goto fail;
+	}
+
+	list_del(&res->res_elm);
+
+	/* be extra careful by clearing the memory before freeing it */
+	memset(res, 0, sizeof(*res));
+
+	kfree(res);
+
+	return 0;
+
+fail:
+	return -EINVAL;
+}
+
+
+int vcm_unreserve(struct res *res)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&vcmlock, flags);
+	ret = __vcm_unreserve(res);
+	spin_unlock_irqrestore(&vcmlock, flags);
+
+	return ret;
+}
+
+
+/* No lock needed, res->len is never updated after creation */
+size_t vcm_get_res_len(struct res *res)
+{
+	if (!res) {
+		vcm_err("res is 0\n");
+		return 0;
+	}
+
+	return res->len;
+}
+
+
+int vcm_set_res_attr(struct res *res, u32 attr)
+{
+	return 0;
+}
+
+
+size_t vcm_get_num_res(struct vcm *vcm)
+{
+	return 0;
+}
+
+
+struct res *vcm_get_next_res(struct vcm *vcm, struct res *res)
+{
+	return 0;
+}
+
+
+size_t vcm_res_copy(struct res *to, size_t to_off, struct res *from,
+		    size_t from_off, size_t len)
+{
+	return 0;
+}
+
+
+size_t vcm_get_min_page_size(void)
+{
+	return PAGE_SIZE;
+}
+
+
+static int vcm_to_smmu_attr(u32 attr)
+{
+	int smmu_attr = 0;
+
+	switch (attr & VCM_CACHE_POLICY) {
+	case VCM_NOTCACHED:
+		smmu_attr = VCM_DEV_ATTR_NONCACHED;
+		break;
+	case VCM_WB_WA:
+		smmu_attr = VCM_DEV_ATTR_CACHED_WB_WA;
+		smmu_attr |= VCM_DEV_ATTR_SH;
+		break;
+	case VCM_WB_NWA:
+		smmu_attr = VCM_DEV_ATTR_CACHED_WB_NWA;
+		smmu_attr |= VCM_DEV_ATTR_SH;
+		break;
+	case VCM_WT:
+		smmu_attr = VCM_DEV_ATTR_CACHED_WT;
+		smmu_attr |= VCM_DEV_ATTR_SH;
+		break;
+	default:
+		return -1;
+	}
+
+	return smmu_attr;
+}
+
+
+/* TBD if you vcm_back again what happens? */
+int vcm_back(struct res *res, struct physmem *physmem)
+{
+	unsigned long flags;
+	struct vcm *vcm;
+	struct phys_chunk *chunk;
+	size_t va = 0;
+	int ret;
+	int attr;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!res) {
+		vcm_err("NULL res\n");
+		goto fail;
+	}
+
+	vcm = res->vcm;
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	switch (vcm->type) {
+	case VCM_DEVICE:
+	case VCM_EXT_KERNEL: /* hack part 1 */
+		attr = vcm_to_smmu_attr(res->attr);
+		if (attr == -1) {
+			vcm_err("Bad SMMU attr\n");
+			goto fail;
+		}
+		break;
+	default:
+		attr = 0;
+		break;
+	}
+
+	if (!physmem) {
+		vcm_err("NULL physmem\n");
+		goto fail;
+	}
+
+	if (res->len == 0) {
+		vcm_err("res->len is 0\n");
+		goto fail;
+	}
+
+	if (physmem->len == 0) {
+		vcm_err("physmem->len is 0\n");
+		goto fail;
+	}
+
+	if (res->len != physmem->len) {
+		vcm_err("res->len (%i) != physmem->len (%i)\n",
+			res->len, physmem->len);
+		goto fail;
+	}
+
+	if (physmem->is_cont) {
+		if (physmem->res == 0) {
+			vcm_err("cont physmem->res is 0");
+			goto fail;
+		}
+	} else {
+		/* fail if no physmem */
+		if (list_empty(&physmem->alloc_head.allocated)) {
+			vcm_err("no allocated phys memory");
+			goto fail;
+		}
+	}
+
+	ret = vcm_no_assoc(res->vcm);
+	if (ret == 1) {
+		vcm_err("can't back un associated VCM\n");
+		goto fail;
+	}
+
+	if (ret == -1) {
+		vcm_err("vcm_no_assoc() ret -1\n");
+		goto fail;
+	}
+
+	ret = vcm_all_activated(res->vcm);
+	if (ret == 0) {
+		vcm_err("can't back, not all associations are activated\n");
+		goto fail_eagain;
+	}
+
+	if (ret == -1) {
+		vcm_err("vcm_all_activated() ret -1\n");
+		goto fail;
+	}
+
+	va = res->dev_addr;
+
+	list_for_each_entry(chunk, &physmem->alloc_head.allocated,
+			    allocated) {
+		struct vcm *vcm = res->vcm;
+		size_t chunk_size =  vcm_alloc_idx_to_size(chunk->size_idx);
+
+		switch (vcm->type) {
+		case VCM_DEVICE:
+		{
+#ifdef CONFIG_SMMU
+			struct avcm *avcm;
+			/* map all */
+			list_for_each_entry(avcm, &vcm->assoc_head,
+					    assoc_elm) {
+
+				ret = smmu_map(
+					(struct smmu_dev *) avcm->dev,
+					chunk->pa, va, chunk_size, attr);
+				if (ret != 0) {
+					vcm_err("smmu_map(%p, %p, %p, 0x%x,"
+						"0x%x)"
+						" ret %i",
+						(void *) avcm->dev,
+						(void *) chunk->pa,
+						(void *) va,
+						(int) chunk_size, attr, ret);
+					goto fail;
+					/* TODO handle weird inter-map case */
+				}
+			}
+			break;
+#else
+			vcm_err("No SMMU support - VCM_DEVICE not supported\n");
+			goto fail;
+#endif
+		}
+
+		case VCM_EXT_KERNEL:
+		{
+			unsigned int pages_in_chunk = chunk_size / PAGE_SIZE;
+			unsigned long loc_va = va;
+			unsigned long loc_pa = chunk->pa;
+
+			const struct mem_type *mtype;
+
+			/* TODO: get this based on MEMTYPE */
+			mtype = get_mem_type(MT_DEVICE);
+			if (!mtype) {
+				vcm_err("mtype is 0\n");
+				goto fail;
+			}
+
+			/* TODO: Map with the same chunk size */
+			while (pages_in_chunk--) {
+				ret = ioremap_page(loc_va,
+						   loc_pa,
+						   mtype);
+				if (ret != 0) {
+					vcm_err("ioremap_page(%p, %p, %p) ret"
+						" %i", (void *) loc_va,
+						(void *) loc_pa,
+						(void *) mtype, ret);
+					goto fail;
+					/* TODO handle weird
+					   inter-map case */
+				}
+
+				/* hack part 2 */
+				/* we're changing the PT entry behind
+				 * linux's back
+				 */
+				ret = cpu_set_attr(loc_va, PAGE_SIZE, attr);
+				if (ret != 0) {
+					vcm_err("cpu_set_attr(%p, %lu, %x)"
+						"ret %i\n",
+						(void *) loc_va, PAGE_SIZE,
+						attr, ret);
+					goto fail;
+					/* TODO handle weird
+					   inter-map case */
+				}
+
+				res->mapped = 1;
+
+				loc_va += PAGE_SIZE;
+				loc_pa += PAGE_SIZE;
+			}
+
+			flush_cache_vmap(va, loc_va);
+			break;
+		}
+		case VCM_ONE_TO_ONE:
+			va = chunk->pa;
+			break;
+		default:
+			/* this should never happen */
+			goto fail;
+		}
+
+		va += chunk_size;
+		/* also add res to the allocated chunk list of refs */
+	}
+
+	/* note the reservation */
+	res->physmem = physmem;
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+fail_eagain:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EAGAIN;
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+
+int vcm_unback(struct res *res)
+{
+	unsigned long flags;
+	struct vcm *vcm;
+	struct physmem *physmem;
+	int ret;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!res)
+		goto fail;
+
+	vcm = res->vcm;
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	if (!res->physmem) {
+		vcm_err("can't unback a non-backed reservation\n");
+		goto fail;
+	}
+
+	physmem = res->physmem;
+	if (!physmem) {
+		vcm_err("physmem is NULL\n");
+		goto fail;
+	}
+
+	if (list_empty(&physmem->alloc_head.allocated)) {
+		vcm_err("physmem allocation is empty\n");
+		goto fail;
+	}
+
+	ret = vcm_no_assoc(res->vcm);
+	if (ret == 1) {
+		vcm_err("can't unback a unassociated reservation\n");
+		goto fail;
+	}
+
+	if (ret == -1) {
+		vcm_err("vcm_no_assoc(%p) ret -1\n", (void *) res->vcm);
+		goto fail;
+	}
+
+	ret = vcm_all_activated(res->vcm);
+	if (ret == 0) {
+		vcm_err("can't unback, not all associations are active\n");
+		goto fail_eagain;
+	}
+
+	if (ret == -1) {
+		vcm_err("vcm_all_activated(%p) ret -1\n", (void *) res->vcm);
+		goto fail;
+	}
+
+
+	switch (vcm->type) {
+	case VCM_EXT_KERNEL:
+		if (!res->mapped) {
+			vcm_err("can't unback an unmapped VCM_EXT_KERNEL"
+				" VCM\n");
+			goto fail;
+		}
+
+		/* vunmap free's vm_area */
+		vunmap(res->vm_area->addr);
+		res->vm_area = 0;
+
+		res->mapped = 0;
+		break;
+
+	case VCM_DEVICE:
+	{
+#ifdef CONFIG_SMMU
+		struct phys_chunk *chunk;
+		size_t va = res->dev_addr;
+
+		list_for_each_entry(chunk, &physmem->alloc_head.allocated,
+				    allocated) {
+			struct vcm *vcm = res->vcm;
+			size_t chunk_size =
+				vcm_alloc_idx_to_size(chunk->size_idx);
+			struct avcm *avcm;
+
+			/* un map all */
+			list_for_each_entry(avcm, &vcm->assoc_head, assoc_elm) {
+				ret = smmu_unmap(
+					(struct smmu_dev *) avcm->dev,
+					va, chunk_size);
+				if (ret != 0) {
+					vcm_err("smmu_unmap(%p, %p, 0x%x)"
+						" ret %i",
+						(void *) avcm->dev,
+						(void *) va,
+						(int) chunk_size, ret);
+					goto fail;
+					/* TODO handle weird inter-unmap state*/
+				}
+			}
+			va += chunk_size;
+			/* may to a light unback, depending on the requested
+			 * functionality
+			 */
+		}
+#else
+		vcm_err("No SMMU support - VCM_DEVICE memory not supported\n");
+		goto fail;
+#endif
+		break;
+	}
+
+	case VCM_ONE_TO_ONE:
+		break;
+	default:
+		/* this should never happen */
+		goto fail;
+	}
+
+	/* clear the reservation */
+	res->physmem = 0;
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+fail_eagain:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EAGAIN;
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+
+enum memtarget_t vcm_get_memtype_of_res(struct res *res)
+{
+	return VCM_INVALID;
+}
+
+static int vcm_free_max_munch_cont(struct phys_chunk *head)
+{
+	struct phys_chunk *chunk, *tmp;
+
+	if (!head)
+		return -1;
+
+	list_for_each_entry_safe(chunk, tmp, &head->allocated,
+				 allocated) {
+		list_del_init(&chunk->allocated);
+	}
+
+	return 0;
+}
+
+static int vcm_alloc_max_munch_cont(size_t start_addr, size_t len,
+				    struct phys_chunk *head)
+{
+	/* this function should always succeed, since it
+	   parallels a VCM */
+
+	int i, j;
+
+	if (!head) {
+		vcm_err("head is NULL in continuous map.\n");
+		goto fail;
+	}
+
+	if (start_addr < __pa(bootmem_cont)) {
+		vcm_err("phys start addr (%p) < base (%p)\n",
+			(void *) start_addr, (void *) __pa(bootmem_cont));
+		goto fail;
+	}
+
+	if ((start_addr + len) >= (__pa(bootmem_cont) + CONT_SZ)) {
+		vcm_err("requested region (%p + %i) > "
+			" available region (%p + %i)",
+			(void *) start_addr, (int) len,
+			(void *) __pa(bootmem_cont), CONT_SZ);
+		goto fail;
+	}
+
+	i = (start_addr - __pa(bootmem_cont))/SZ_4K;
+
+	for (j = 0; j < ARRAY_SIZE(chunk_sizes); ++j) {
+		while (len/chunk_sizes[j]) {
+			if (!list_empty(&cont_phys_chunk[i].allocated)) {
+				vcm_err("chunk %i ( addr %p) already mapped\n",
+					i, (void *) (start_addr +
+						     (i*chunk_sizes[j])));
+				goto fail_free;
+			}
+			list_add_tail(&cont_phys_chunk[i].allocated,
+				      &head->allocated);
+			cont_phys_chunk[i].size_idx = j;
+
+			len -= chunk_sizes[j];
+			i += chunk_sizes[j]/SZ_4K;
+		}
+	}
+
+	if (len % SZ_4K) {
+		if (!list_empty(&cont_phys_chunk[i].allocated)) {
+			vcm_err("chunk %i (addr %p) already mapped\n",
+				i, (void *) (start_addr + (i*SZ_4K)));
+			goto fail_free;
+		}
+		len -= SZ_4K;
+		list_add_tail(&cont_phys_chunk[i].allocated,
+			      &head->allocated);
+
+		i++;
+	}
+
+	return i;
+
+fail_free:
+	{
+		struct phys_chunk *chunk, *tmp;
+		/* just remove from list, if we're double alloc'ing
+		   we don't want to stamp on the other guy */
+		list_for_each_entry_safe(chunk, tmp, &head->allocated,
+					 allocated) {
+			list_del(&chunk->allocated);
+		}
+	}
+fail:
+	return 0;
+}
+
+struct physmem *vcm_phys_alloc(enum memtype_t memtype, size_t len,
+			       u32 attr)
+{
+	unsigned long flags;
+	int ret;
+	struct physmem *physmem = NULL;
+	int blocks_allocated;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	physmem = kzalloc(sizeof(*physmem), GFP_KERNEL);
+	if (!physmem) {
+		vcm_err("physmem is NULL\n");
+		goto fail;
+	}
+
+	physmem->memtype = memtype;
+	physmem->len = len;
+	physmem->attr = attr;
+
+	INIT_LIST_HEAD(&physmem->alloc_head.allocated);
+
+	if (attr & VCM_PHYS_CONT) {
+		if (!cont_vcm) {
+			vcm_err("cont_vcm is NULL\n");
+			goto fail2;
+		}
+
+		physmem->is_cont = 1;
+
+		/* TODO: get attributes */
+		physmem->res = __vcm_reserve(cont_vcm, len, 0);
+		if (physmem->res == 0) {
+			vcm_err("contiguous space allocation failed\n");
+			goto fail2;
+		}
+
+		/* if we're here we know we have memory, create
+		   the shadow physmem links*/
+		blocks_allocated =
+			vcm_alloc_max_munch_cont(
+				physmem->res->dev_addr,
+				len,
+				&physmem->alloc_head);
+
+		if (blocks_allocated == 0) {
+			vcm_err("shadow physmem allocation failed\n");
+			goto fail3;
+		}
+	} else {
+		blocks_allocated = vcm_alloc_max_munch(len,
+						       &physmem->alloc_head);
+		if (blocks_allocated == 0) {
+			vcm_err("physical allocation failed:"
+				" vcm_alloc_max_munch(%i, %p) ret 0\n",
+				len, &physmem->alloc_head);
+			goto fail2;
+		}
+	}
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return physmem;
+
+fail3:
+	ret = __vcm_unreserve(physmem->res);
+	if (ret != 0) {
+		vcm_err("vcm_unreserve(%p) ret %i during cleanup",
+			(void *) physmem->res, ret);
+		spin_unlock_irqrestore(&vcmlock, flags);
+		return 0;
+	}
+fail2:
+	kfree(physmem);
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+}
+
+
+int vcm_phys_free(struct physmem *physmem)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!physmem) {
+		vcm_err("physmem is NULL\n");
+		goto fail;
+	}
+
+	if (physmem->is_cont) {
+		if (physmem->res == 0) {
+			vcm_err("contiguous reservation is NULL\n");
+			goto fail;
+		}
+
+		ret = vcm_free_max_munch_cont(&physmem->alloc_head);
+		if (ret != 0) {
+			vcm_err("failed to free physical blocks:"
+				" vcm_free_max_munch_cont(%p) ret %i\n",
+				(void *) &physmem->alloc_head, ret);
+			goto fail;
+		}
+
+		ret = __vcm_unreserve(physmem->res);
+		if (ret != 0) {
+			vcm_err("failed to free virtual blocks:"
+				" vcm_unreserve(%p) ret %i\n",
+				(void *) physmem->res, ret);
+			goto fail;
+		}
+
+	} else {
+
+		ret = vcm_alloc_free_blocks(&physmem->alloc_head);
+		if (ret != 0) {
+			vcm_err("failed to free physical blocks:"
+				" vcm_alloc_free_blocks(%p) ret %i\n",
+				(void *) &physmem->alloc_head, ret);
+			goto fail;
+		}
+	}
+
+	memset(physmem, 0, sizeof(*physmem));
+
+	kfree(physmem);
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+
+struct avcm *vcm_assoc(struct vcm *vcm, struct device *dev, u32 attr)
+{
+	unsigned long flags;
+	struct avcm *avcm = NULL;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!vcm) {
+		vcm_err("vcm is NULL\n");
+		goto fail;
+	}
+
+	if (!dev) {
+		vcm_err("dev is NULL\n");
+		goto fail;
+	}
+
+	if (vcm->type == VCM_EXT_KERNEL && !list_empty(&vcm->assoc_head)) {
+		vcm_err("only one device may be assocoated with a"
+			" VCM_EXT_KERNEL\n");
+		goto fail;
+	}
+
+	avcm = kzalloc(sizeof(*avcm), GFP_KERNEL);
+	if (!avcm) {
+		vcm_err("kzalloc(%i, GFP_KERNEL) ret NULL\n", sizeof(*avcm));
+		goto fail;
+	}
+
+	avcm->dev = dev;
+
+	avcm->vcm = vcm;
+	avcm->attr = attr;
+	avcm->is_active = 0;
+
+	INIT_LIST_HEAD(&avcm->assoc_elm);
+	list_add(&avcm->assoc_elm, &vcm->assoc_head);
+
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return avcm;
+
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+}
+
+
+int vcm_deassoc(struct avcm *avcm)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!avcm) {
+		vcm_err("avcm is NULL\n");
+		goto fail;
+	}
+
+	if (list_empty(&avcm->assoc_elm)) {
+		vcm_err("nothing to deassociate\n");
+		goto fail;
+	}
+
+	if (avcm->is_active) {
+		vcm_err("association still activated\n");
+		goto fail_busy;
+	}
+
+	list_del(&avcm->assoc_elm);
+
+	memset(avcm, 0, sizeof(*avcm));
+
+	kfree(avcm);
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+fail_busy:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EBUSY;
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+
+int vcm_set_assoc_attr(struct avcm *avcm, u32 attr)
+{
+	return 0;
+}
+
+
+int vcm_activate(struct avcm *avcm)
+{
+	unsigned long flags;
+	struct vcm *vcm;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!avcm) {
+		vcm_err("avcm is NULL\n");
+		goto fail;
+	}
+
+	vcm = avcm->vcm;
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	if (!avcm->dev) {
+		vcm_err("cannot activate without a device\n");
+		goto fail_nodev;
+	}
+
+	if (avcm->is_active) {
+		vcm_err("double activate\n");
+		goto fail_busy;
+	}
+
+	if (vcm->type == VCM_DEVICE) {
+#ifdef CONFIG_SMMU
+		int ret = smmu_is_active((struct smmu_dev *) avcm->dev);
+		if (ret == -1) {
+			vcm_err("smmu_is_active(%p) ret -1\n",
+				(void *) avcm->dev);
+			goto fail_dev;
+		}
+
+		if (ret == 1) {
+			vcm_err("SMMU is already active\n");
+			goto fail_busy;
+		}
+
+		/* TODO, pmem check */
+		ret = smmu_activate((struct smmu_dev *) avcm->dev);
+		if (ret != 0) {
+			vcm_err("smmu_activate(%p) ret %i"
+				" SMMU failed to activate\n",
+				(void *) avcm->dev, ret);
+			goto fail_dev;
+		}
+#else
+		vcm_err("No SMMU support - cannot activate/deactivate\n");
+		goto fail_nodev;
+#endif
+	}
+
+	avcm->is_active = 1;
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+
+#ifdef CONFIG_SMMU
+fail_dev:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -1;
+#endif
+fail_busy:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EBUSY;
+fail_nodev:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -ENODEV;
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+
+int vcm_deactivate(struct avcm *avcm)
+{
+	unsigned long flags;
+	struct vcm *vcm;
+
+	spin_lock_irqsave(&vcmlock, flags);
+
+	if (!avcm)
+		goto fail;
+
+	vcm = avcm->vcm;
+	if (!vcm) {
+		vcm_err("NULL vcm\n");
+		goto fail;
+	}
+
+	if (!avcm->dev) {
+		vcm_err("cannot deactivate without a device\n");
+		goto fail;
+	}
+
+	if (!avcm->is_active) {
+		vcm_err("double deactivate\n");
+		goto fail_nobusy;
+	}
+
+	if (vcm->type == VCM_DEVICE) {
+#ifdef CONFIG_SMMU
+		int ret = smmu_is_active((struct smmu_dev *) avcm->dev);
+		if (ret == -1) {
+			vcm_err("smmu_is_active(%p) ret %i\n",
+				(void *) avcm->dev, ret);
+			goto fail_dev;
+		}
+
+		if (ret == 0) {
+			vcm_err("double SMMU deactivation\n");
+			goto fail_nobusy;
+		}
+
+		/* TODO, pmem check */
+		ret = smmu_deactivate((struct smmu_dev *) avcm->dev);
+		if (ret != 0) {
+			vcm_err("smmu_deactivate(%p) ret %i\n",
+				(void *) avcm->dev, ret);
+			goto fail_dev;
+		}
+#else
+		vcm_err("No SMMU support - cannot activate/deactivate\n");
+		goto fail;
+#endif
+	}
+
+	avcm->is_active = 0;
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return 0;
+#ifdef CONFIG_SMMU
+fail_dev:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -1;
+#endif
+fail_nobusy:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -ENOENT;
+fail:
+	spin_unlock_irqrestore(&vcmlock, flags);
+	return -EINVAL;
+}
+
+struct bound *vcm_create_bound(struct vcm *vcm, size_t len)
+{
+	return 0;
+}
+
+
+int vcm_free_bound(struct bound *bound)
+{
+	return -1;
+}
+
+
+struct res *vcm_reserve_from_bound(struct bound *bound, size_t len,
+				   u32 attr)
+{
+	return 0;
+}
+
+
+size_t vcm_get_bound_start_addr(struct bound *bound)
+{
+	return 0;
+}
+
+
+size_t vcm_get_bound_len(struct bound *bound)
+{
+	return 0;
+}
+
+
+struct physmem *vcm_map_phys_addr(size_t phys, size_t len)
+{
+	return 0;
+}
+
+
+size_t vcm_get_next_phys_addr(struct physmem *physmem, size_t phys, size_t *len)
+{
+	return 0;
+}
+
+struct res *vcm_get_res(unsigned long dev_addr, struct vcm *vcm)
+{
+	return 0;
+}
+
+
+size_t vcm_translate(size_t src_dev, struct vcm *src_vcm, struct vcm *dst_vcm)
+{
+	return 0;
+}
+
+
+size_t vcm_get_phys_num_res(size_t phys)
+{
+	return 0;
+}
+
+
+struct res *vcm_get_next_phys_res(size_t phys, struct res *res, size_t *len)
+{
+	return 0;
+}
+
+
+size_t vcm_get_pgtbl_pa(struct vcm *vcm)
+{
+	return 0;
+}
+
+
+/* No lock needed, smmu_translate has its own lock */
+size_t vcm_dev_addr_to_phys_addr(struct device *dev, unsigned long dev_addr)
+{
+#ifdef CONFIG_SMMU
+	int ret;
+	ret = smmu_translate((struct smmu_dev *) dev, dev_addr);
+	if (ret == -1)
+		vcm_err("smmu_translate(%p, %p) ret %i\n",
+			(void *) dev, (void *) dev_addr, ret);
+
+	return ret;
+#else
+	vcm_err("No support for SMMU - manual translation not supported\n");
+	return -1;
+#endif
+}
+
+
+/* No lock needed, bootmem_cont never changes after  */
+size_t vcm_get_cont_memtype_pa(enum memtype_t memtype)
+{
+	if (memtype != VCM_MEMTYPE_0) {
+		vcm_err("memtype != VCM_MEMTYPE_0\n");
+		goto fail;
+	}
+
+	if (!bootmem_cont) {
+		vcm_err("bootmem_cont 0\n");
+		goto fail;
+	}
+
+	return (size_t) __pa(bootmem_cont);
+fail:
+	return 0;
+}
+
+
+/* No lock needed, constant */
+size_t vcm_get_cont_memtype_len(enum memtype_t memtype)
+{
+	if (memtype != VCM_MEMTYPE_0) {
+		vcm_err("memtype != VCM_MEMTYPE_0\n");
+		return 0;
+	}
+
+	return CONT_SZ;
+}
+
+int vcm_hook(struct device *dev, vcm_handler handler, void *data)
+{
+#ifdef CONFIG_SMMU
+	int ret;
+
+	ret = smmu_hook_irpt((struct smmu_dev *) dev, handler, data);
+	if (ret != 0)
+		vcm_err("smmu_hook_irpt(%p, %p, %p) ret %i\n", (void *) dev,
+			(void *) handler, (void *) data, ret);
+
+	return ret;
+#else
+	vcm_err("No support for SMMU - interrupts not supported\n");
+	return -1;
+#endif
+}
+
+
+size_t vcm_hw_ver(struct device *dev)
+{
+	return 0;
+}
+
+
+static int vcm_cont_phys_chunk_init(void)
+{
+	int i;
+	int cont_pa;
+
+	if (!cont_phys_chunk) {
+		vcm_err("cont_phys_chunk 0\n");
+		goto fail;
+	}
+
+	if (!bootmem_cont) {
+		vcm_err("bootmem_cont 0\n");
+		goto fail;
+	}
+
+	cont_pa = (int) __pa(bootmem_cont);
+
+	for (i = 0; i < CONT_SZ/PAGE_SIZE; ++i) {
+		cont_phys_chunk[i].pa = (int) cont_pa; cont_pa += PAGE_SIZE;
+		cont_phys_chunk[i].size_idx = IDX_4K;
+		INIT_LIST_HEAD(&cont_phys_chunk[i].allocated);
+	}
+
+	return 0;
+
+fail:
+	return -1;
+}
+
+
+int vcm_sys_init(void)
+{
+	int ret;
+	printk(KERN_INFO "VCM Initialization\n");
+	if (!bootmem) {
+		vcm_err("bootmem is 0\n");
+		ret = -1;
+		goto fail;
+	}
+
+	if (!bootmem_cont) {
+		vcm_err("bootmem_cont is 0\n");
+		ret = -1;
+		goto fail;
+	}
+
+	ret = vcm_setup_tex_classes();
+	if (ret != 0) {
+		printk(KERN_INFO "Could not determine TEX attribute mapping\n");
+		ret = -1;
+		goto fail;
+	}
+
+
+	ret = vcm_alloc_init(__pa(bootmem));
+	if (ret != 0) {
+		vcm_err("vcm_alloc_init(%p) ret %i\n", (void *) __pa(bootmem),
+			ret);
+		ret = -1;
+		goto fail;
+	}
+
+	cont_phys_chunk = kzalloc(sizeof(*cont_phys_chunk)*(CONT_SZ/PAGE_SIZE),
+				  GFP_KERNEL);
+	if (!cont_phys_chunk) {
+		vcm_err("kzalloc(%lu, GFP_KERNEL) ret 0",
+			sizeof(*cont_phys_chunk)*(CONT_SZ/PAGE_SIZE));
+		goto fail_free;
+	}
+
+	/* the address and size will hit our special case unless we
+	   pass an override */
+	cont_vcm = vcm_create_flagged(0, __pa(bootmem_cont), CONT_SZ);
+	if (cont_vcm == 0) {
+		vcm_err("vcm_create_flagged(0, %p, %i) ret 0\n",
+			(void *) __pa(bootmem_cont), CONT_SZ);
+		ret = -1;
+		goto fail_free2;
+	}
+
+	ret = vcm_cont_phys_chunk_init();
+	if (ret != 0) {
+		vcm_err("vcm_cont_phys_chunk_init() ret %i\n", ret);
+		goto fail_free3;
+	}
+
+	printk(KERN_INFO "VCM Initialization OK\n");
+	return 0;
+
+fail_free3:
+	ret = __vcm_free(cont_vcm);
+	if (ret != 0) {
+		vcm_err("vcm_free(%p) ret %i during failure path\n",
+			(void *) cont_vcm, ret);
+		return -1;
+	}
+
+fail_free2:
+	kfree(cont_phys_chunk);
+	cont_phys_chunk = 0;
+
+fail_free:
+	ret = vcm_alloc_destroy();
+	if (ret != 0)
+		vcm_err("vcm_alloc_destroy() ret %i during failure path\n",
+			ret);
+
+	ret = -1;
+fail:
+	return ret;
+}
+
+
+int vcm_sys_destroy(void)
+{
+	int ret = 0;
+
+	if (!cont_phys_chunk) {
+		vcm_err("cont_phys_chunk is 0\n");
+		return -1;
+	}
+
+	if (!cont_vcm) {
+		vcm_err("cont_vcm is 0\n");
+		return -1;
+	}
+
+	ret = __vcm_free(cont_vcm);
+	if (ret != 0) {
+		vcm_err("vcm_free(%p) ret %i\n", (void *) cont_vcm, ret);
+		return -1;
+	}
+
+	cont_vcm = 0;
+
+	kfree(cont_phys_chunk);
+	cont_phys_chunk = 0;
+
+	ret = vcm_alloc_destroy();
+	if (ret != 0) {
+		vcm_err("vcm_alloc_destroy() ret %i\n", ret);
+		return -1;
+	}
+
+	return ret;
+}
+
+int vcm_init(void)
+{
+	int ret;
+
+	bootmem = __alloc_bootmem(BOOTMEM_SZ, BOOTMEM_ALIGN, 0);
+	if (!bootmem) {
+		vcm_err("segregated block pool alloc failed:"
+			" __alloc_bootmem(%i, %i, 0)\n",
+			BOOTMEM_SZ, BOOTMEM_ALIGN);
+		goto fail;
+	}
+
+	bootmem_cont = __alloc_bootmem(CONT_SZ, CONT_ALIGN, 0);
+	if (!bootmem_cont) {
+		vcm_err("contiguous pool alloc failed:"
+			" __alloc_bootmem(%i, %i, 0)\n",
+			CONT_SZ, CONT_ALIGN);
+		goto fail_free;
+	}
+
+	ret = vcm_sys_init();
+	if (ret != 0) {
+		vcm_err("vcm_sys_init() ret %i\n", ret);
+		goto fail_free2;
+	}
+
+	return 0;
+
+fail_free2:
+	free_bootmem(__pa(bootmem_cont), CONT_SZ);
+fail_free:
+	free_bootmem(__pa(bootmem), BOOTMEM_SZ);
+fail:
+	return -1;
+};
+
+/* Useful for testing, and if VCM is ever unloaded */
+void vcm_exit(void)
+{
+	int ret;
+
+	if (!bootmem_cont) {
+		vcm_err("bootmem_cont is 0\n");
+		goto fail;
+	}
+
+	if (!bootmem) {
+		vcm_err("bootmem is 0\n");
+		goto fail;
+	}
+
+	ret = vcm_sys_destroy();
+	if (ret != 0) {
+		vcm_err("vcm_sys_destroy() ret %i\n", ret);
+		goto fail;
+	}
+
+	free_bootmem(__pa(bootmem_cont), CONT_SZ);
+	free_bootmem(__pa(bootmem), BOOTMEM_SZ);
+fail:
+	return;
+}
+early_initcall(vcm_init);
+module_exit(vcm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Zach Pfeffer <[email protected]>");
include/linux/vcm.h new file +661 -0

661 lines not on this page · raw message

include/linux/vcm_types.h new file +338 -0

338 lines not on this page · raw message

Signature · 11 lines
1.7.0.2

--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>