msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142 Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
1831 lines
33 KiB
C
1831 lines
33 KiB
C
/* 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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vcm_mm.h>
|
|
#include <linux/vcm.h>
|
|
#include <linux/vcm_alloc.h>
|
|
#include <linux/vcm_types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <asm/page.h>
|
|
#include <asm/sizes.h>
|
|
|
|
#include <linux/iommu.h>
|
|
|
|
/* alloc_vm_area */
|
|
#include <linux/pfn.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/mach/map.h>
|
|
|
|
#define ONE_TO_ONE_CHK 1
|
|
|
|
#define vcm_err(a, ...) \
|
|
pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__)
|
|
|
|
static unsigned int smmu_map_sizes[4] = {SZ_16M, SZ_1M, SZ_64K, SZ_4K};
|
|
|
|
static phys_addr_t *bootmem_cont;
|
|
static int cont_sz;
|
|
static struct vcm *cont_vcm_id;
|
|
static struct phys_chunk *cont_phys_chunk;
|
|
|
|
DEFINE_SPINLOCK(vcmlock);
|
|
|
|
/* Leaving this in for now to keep compatibility of the API. */
|
|
/* This will disappear. */
|
|
phys_addr_t vcm_get_dev_addr(struct res *res)
|
|
{
|
|
if (!res) {
|
|
vcm_err("NULL RES");
|
|
return -EINVAL;
|
|
}
|
|
return res->dev_addr;
|
|
}
|
|
|
|
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 -EINVAL;
|
|
}
|
|
|
|
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, unsigned long 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);
|
|
ret = -EINVAL;
|
|
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 fail;
|
|
}
|
|
|
|
vcm->domain = iommu_domain_alloc();
|
|
if (!vcm->domain) {
|
|
vcm_err("Could not allocate domain\n");
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
fail:
|
|
if (ret && vcm->pool)
|
|
gen_pool_destroy(vcm->pool);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static struct vcm *vcm_create_flagged(int flag, unsigned long 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 &&
|
|
start_addr == (size_t) 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 -EINVAL;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
if (vcm->domain)
|
|
iommu_domain_free(vcm->domain);
|
|
|
|
vcm->domain = NULL;
|
|
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;
|
|
int align_attr = 0, i = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
align_attr = (attr >> VCM_ALIGN_SHIFT) & VCM_ALIGN_MASK;
|
|
|
|
if (align_attr >= 32) {
|
|
vcm_err("Invalid alignment attribute: %d\n", align_attr);
|
|
goto fail2;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&res->res_elm);
|
|
res->vcm = vcm;
|
|
res->len = len;
|
|
res->attr = attr;
|
|
res->alignment_req = smmu_map_sizes[ARRAY_SIZE(smmu_map_sizes) - 1];
|
|
|
|
if (align_attr == 0) {
|
|
for (i = 0; i < ARRAY_SIZE(smmu_map_sizes); i++)
|
|
if (len / smmu_map_sizes[i]) {
|
|
res->alignment_req = smmu_map_sizes[i];
|
|
break;
|
|
}
|
|
} else
|
|
res->alignment_req = 1 << align_attr;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
u32 vcm_get_res_attr(struct res *res)
|
|
{
|
|
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 -EINVAL;
|
|
}
|
|
|
|
return smmu_attr;
|
|
}
|
|
|
|
|
|
static int vcm_process_chunk(struct iommu_domain *domain, phys_addr_t pa,
|
|
unsigned long va, size_t len, u32 attr, int map)
|
|
{
|
|
int ret, i, map_order;
|
|
unsigned long map_len = smmu_map_sizes[ARRAY_SIZE(smmu_map_sizes) - 1];
|
|
|
|
for (i = 0; i < ARRAY_SIZE(smmu_map_sizes); i++) {
|
|
if (IS_ALIGNED(va, smmu_map_sizes[i]) && len >=
|
|
smmu_map_sizes[i]) {
|
|
map_len = smmu_map_sizes[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef VCM_PERF_DEBUG
|
|
if (va & (len - 1))
|
|
pr_warning("Warning! Suboptimal VCM mapping alignment "
|
|
"va = %p, len = %p. Expect TLB performance "
|
|
"degradation.\n", (void *) va, (void *) len);
|
|
#endif
|
|
|
|
map_order = get_order(map_len);
|
|
|
|
while (len) {
|
|
if (va & (SZ_4K - 1)) {
|
|
vcm_err("Tried to map w/ align < 4k! va = %08lx\n", va);
|
|
goto fail;
|
|
}
|
|
|
|
if (map_len > len) {
|
|
vcm_err("map_len = %lu, len = %d, trying to overmap\n",
|
|
map_len, len);
|
|
goto fail;
|
|
}
|
|
|
|
if (map)
|
|
ret = iommu_map(domain, va, pa, map_order, attr);
|
|
else
|
|
ret = iommu_unmap(domain, va, map_order);
|
|
|
|
if (ret) {
|
|
vcm_err("iommu_map/unmap(%p, %p, %p, 0x%x, 0x%x) ret %i"
|
|
"map = %d", (void *) domain, (void *) pa,
|
|
(void *) va, (int) map_len, attr, ret, map);
|
|
goto fail;
|
|
}
|
|
|
|
va += map_len;
|
|
pa += map_len;
|
|
len -= map_len;
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 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 = chunk->size;
|
|
|
|
if (chunk_size <= 0) {
|
|
vcm_err("Bad chunk size: %d\n", chunk_size);
|
|
goto fail;
|
|
}
|
|
|
|
switch (vcm->type) {
|
|
case VCM_DEVICE:
|
|
{
|
|
/* map all */
|
|
ret = vcm_process_chunk(vcm->domain, chunk->pa,
|
|
va, chunk_size, attr, 1);
|
|
if (ret != 0) {
|
|
vcm_err("vcm_process_chunk(%p, %p, %p,"
|
|
" 0x%x, 0x%x)"
|
|
" ret %i",
|
|
vcm->domain,
|
|
(void *) chunk->pa,
|
|
(void *) va,
|
|
(int) chunk_size, attr, ret);
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
|
|
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:
|
|
{
|
|
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 = chunk->size;
|
|
|
|
ret = vcm_process_chunk(vcm->domain, 0, va,
|
|
chunk_size, 0, 0);
|
|
if (ret != 0) {
|
|
vcm_err("vcm_unback_chunk(%p, %p, 0x%x)"
|
|
" ret %i",
|
|
(void *) vcm->domain,
|
|
(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
|
|
*/
|
|
}
|
|
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 -EINVAL;
|
|
|
|
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 < (int) bootmem_cont) {
|
|
vcm_err("phys start addr (%p) < base (%p)\n",
|
|
(void *) start_addr, (void *) bootmem_cont);
|
|
goto fail;
|
|
}
|
|
|
|
if ((start_addr + len) >= ((size_t) bootmem_cont + cont_sz)) {
|
|
vcm_err("requested region (%p + %i) > "
|
|
" available region (%p + %i)",
|
|
(void *) start_addr, (int) len,
|
|
(void *) bootmem_cont, cont_sz);
|
|
goto fail;
|
|
}
|
|
|
|
i = (start_addr - (size_t) bootmem_cont)/SZ_4K;
|
|
|
|
for (j = 0; j < ARRAY_SIZE(smmu_map_sizes); ++j) {
|
|
while (len/smmu_map_sizes[j]) {
|
|
if (!list_empty(&cont_phys_chunk[i].allocated)) {
|
|
vcm_err("chunk %i ( addr %p) already mapped\n",
|
|
i, (void *) (start_addr +
|
|
(i*smmu_map_sizes[j])));
|
|
goto fail_free;
|
|
}
|
|
list_add_tail(&cont_phys_chunk[i].allocated,
|
|
&head->allocated);
|
|
cont_phys_chunk[i].size = smmu_map_sizes[j];
|
|
|
|
len -= smmu_map_sizes[j];
|
|
i += smmu_map_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_id) {
|
|
vcm_err("cont_vcm_id is NULL\n");
|
|
goto fail2;
|
|
}
|
|
|
|
physmem->is_cont = 1;
|
|
|
|
/* TODO: get attributes */
|
|
physmem->res = __vcm_reserve(cont_vcm_id, 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, memtype,
|
|
&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->memtype,
|
|
&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_id 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;
|
|
}
|
|
|
|
|
|
u32 vcm_get_assoc_attr(struct avcm *avcm)
|
|
{
|
|
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;
|
|
ret = iommu_attach_device(vcm->domain, avcm->dev);
|
|
if (ret != 0) {
|
|
dev_err(avcm->dev, "failed to attach to domain\n");
|
|
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 -ENODEV;
|
|
#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
|
|
/* TODO, pmem check */
|
|
iommu_detach_device(vcm->domain, avcm->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;
|
|
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 -EINVAL;
|
|
}
|
|
|
|
|
|
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(phys_addr_t phys, size_t len)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
size_t vcm_get_next_phys_addr(struct physmem *physmem, phys_addr_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(struct device *src_dev, struct vcm *src_vcm,
|
|
struct vcm *dst_vcm)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
size_t vcm_get_phys_num_res(phys_addr_t phys)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct res *vcm_get_next_phys_res(phys_addr_t phys, struct res *res,
|
|
size_t *len)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
phys_addr_t vcm_get_pgtbl_pa(struct vcm *vcm)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* No lock needed, smmu_translate has its own lock */
|
|
phys_addr_t vcm_dev_addr_to_phys_addr(struct vcm *vcm, unsigned long dev_addr)
|
|
{
|
|
if (!vcm)
|
|
return -EINVAL;
|
|
#ifdef CONFIG_SMMU
|
|
return iommu_iova_to_phys(vcm->domain, dev_addr);
|
|
#else
|
|
vcm_err("No support for SMMU - manual translation not supported\n");
|
|
return -ENODEV;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* No lock needed, bootmem_cont never changes after */
|
|
phys_addr_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) 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
|
|
vcm_err("No interrupts in IOMMU API\n");
|
|
return -ENODEV;
|
|
#else
|
|
vcm_err("No support for SMMU - interrupts not supported\n");
|
|
return -ENODEV;
|
|
#endif
|
|
}
|
|
|
|
|
|
size_t vcm_hw_ver(size_t 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 = (size_t) bootmem_cont;
|
|
|
|
for (i = 0; i < cont_sz/PAGE_SIZE; ++i) {
|
|
cont_phys_chunk[i].pa = cont_pa; cont_pa += PAGE_SIZE;
|
|
cont_phys_chunk[i].size = SZ_4K;
|
|
/* Not part of an allocator-managed pool */
|
|
cont_phys_chunk[i].pool_idx = -1;
|
|
INIT_LIST_HEAD(&cont_phys_chunk[i].allocated);
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -EINVAL;
|
|
}
|
|
|
|
int vcm_sys_init(struct physmem_region *mem, int n_regions,
|
|
struct vcm_memtype_map *mt_map, int n_mt,
|
|
void *cont_pa, unsigned int cont_len)
|
|
{
|
|
int ret;
|
|
printk(KERN_INFO "VCM Initialization\n");
|
|
bootmem_cont = cont_pa;
|
|
cont_sz = cont_len;
|
|
|
|
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(mem, n_regions, mt_map, n_mt);
|
|
|
|
if (ret != 0) {
|
|
vcm_err("vcm_alloc_init() ret %i\n", 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_id = vcm_create_flagged(0, (size_t)bootmem_cont, cont_sz);
|
|
if (cont_vcm_id == 0) {
|
|
vcm_err("vcm_create_flagged(0, %p, %i) ret 0\n",
|
|
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_id);
|
|
if (ret != 0) {
|
|
vcm_err("vcm_free(%p) ret %i during failure path\n",
|
|
(void *) cont_vcm_id, ret);
|
|
return ret;
|
|
}
|
|
|
|
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 = -EINVAL;
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
|
|
int vcm_sys_destroy(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!cont_phys_chunk) {
|
|
vcm_err("cont_phys_chunk is 0\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!cont_vcm_id) {
|
|
vcm_err("cont_vcm_id is 0\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = __vcm_free(cont_vcm_id);
|
|
if (ret != 0) {
|
|
vcm_err("vcm_free(%p) ret %i\n", (void *) cont_vcm_id, ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cont_vcm_id = 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 ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Zach Pfeffer <zpfeffer@codeaurora.org>");
|