static const struct kgsl_ioctl kgsl_ioctl_funcs[] = {
...
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC,
kgsl_ioctl_gpumem_alloc),
...
}
1. kgsl_gpumem_alloc
struct kgsl_gpumem_alloc {
unsigned long gpuaddr;
__kernel_size_t size;
unsigned int flags;
};
#define IOCTL_KGSL_GPUMEM_ALLOC \
_IOWR(KGSL_IOC_TYPE, 0x2f, struct kgsl_gpumem_alloc)
2. kgsl_ioctl_gpumem_alloc
long kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
struct kgsl_device *device = dev_priv->device;
struct kgsl_gpumem_alloc *param = data;
struct kgsl_mem_entry *entry;
uint64_t flags = param->flags;
if ((param->flags & KGSL_MEMFLAGS_SECURE) && is_compat_task()
&& test_bit(KGSL_MMU_64BIT, &device->mmu.features))
return -EOPNOTSUPP;
flags &= ~((uint64_t) KGSL_MEMFLAGS_USE_CPU_MAP);
if (is_compat_task())
flags |= KGSL_MEMFLAGS_FORCE_32BIT;
entry = gpumem_alloc_entry(dev_priv, (uint64_t) param->size, flags);
if (IS_ERR(entry))
return PTR_ERR(entry);
param->gpuaddr = (unsigned long) entry->memdesc.gpuaddr;
param->size = (size_t) entry->memdesc.size;
param->flags = (unsigned int) entry->memdesc.flags;
kgsl_mem_entry_put(entry);
return 0;
}
2.1 kgsl_mem_entry
struct kgsl_mem_entry {
struct kref refcount;
struct kgsl_memdesc memdesc;
void *priv_data;
struct rb_node node;
unsigned int id;
struct kgsl_process_private *priv;
int pending_free;
char metadata[KGSL_GPUOBJ_ALLOC_METADATA_MAX + 1];
struct work_struct work;
atomic_t map_count;
};
2.1.1 kgsl_memdesc
struct kgsl_memdesc {
struct kgsl_pagetable *pagetable;
void *hostptr;
unsigned int hostptr_count;
uint64_t gpuaddr;
phys_addr_t physaddr;
uint64_t size;
unsigned int priv;
struct sg_table *sgt;
const struct kgsl_memdesc_ops *ops;
uint64_t flags;
struct device *dev;
unsigned long attrs;
struct page **pages;
unsigned int page_count;
spinlock_t lock;
struct file *shmem_filp;
struct rb_root_cached ranges;
struct mutex ranges_lock;
u32 gmuaddr;
};
2.1.2 kgsl_memdesc_ops
struct kgsl_memdesc_ops {
unsigned int vmflags;
vm_fault_t (*vmfault)(struct kgsl_memdesc *memdesc,
struct vm_area_struct *vma, struct vm_fault *vmf);
void (*free)(struct kgsl_memdesc *memdesc);
int (*map_kernel)(struct kgsl_memdesc *memdesc);
void (*unmap_kernel)(struct kgsl_memdesc *memdesc);
void (*put_gpuaddr)(struct kgsl_memdesc *memdesc);
};
2.2 gpumem_alloc_entry
struct kgsl_mem_entry *gpumem_alloc_entry(
struct kgsl_device_private *dev_priv,
uint64_t size, uint64_t flags)
{
int ret;
struct kgsl_process_private *private = dev_priv->process_priv;
struct kgsl_mem_entry *entry;
struct kgsl_device *device = dev_priv->device;
u32 cachemode;
if (BITS_PER_LONG == 32)
flags &= ~((uint64_t) KGSL_MEMFLAGS_FORCE_32BIT);
if (flags & KGSL_MEMFLAGS_VBO)
return gpumem_alloc_vbo_entry(dev_priv, size, flags);
flags &= KGSL_MEMFLAGS_GPUREADONLY
| KGSL_CACHEMODE_MASK
| KGSL_MEMTYPE_MASK
| KGSL_MEMALIGN_MASK
| KGSL_MEMFLAGS_USE_CPU_MAP
| KGSL_MEMFLAGS_SECURE
| KGSL_MEMFLAGS_FORCE_32BIT
| KGSL_MEMFLAGS_IOCOHERENT
| KGSL_MEMFLAGS_GUARD_PAGE;
if ((flags & KGSL_MEMFLAGS_SECURE) && !check_and_warn_secured(device))
return ERR_PTR(-EOPNOTSUPP);
flags = cap_alignment(device, flags);
if (size == 0 || size > UINT_MAX)
return ERR_PTR(-EINVAL);
flags = kgsl_filter_cachemode(flags);
entry = kgsl_mem_entry_create();
if (entry == NULL)
return ERR_PTR(-ENOMEM);
if (IS_ENABLED(CONFIG_QCOM_KGSL_IOCOHERENCY_DEFAULT) &&
kgsl_cachemode_is_cached(flags))
flags |= KGSL_MEMFLAGS_IOCOHERENT;
ret = kgsl_allocate_user(device, &entry->memdesc,
size, flags, 0);
if (ret != 0)
goto err;
ret = kgsl_mem_entry_attach_and_map(device, private, entry);
if (ret != 0) {
kgsl_sharedmem_free(&entry->memdesc);
goto err;
}
cachemode = kgsl_memdesc_get_cachemode(&entry->memdesc);
if (!(flags & KGSL_MEMFLAGS_SECURE) &&
!(cachemode == KGSL_CACHEMODE_WRITEBACK) &&
!(cachemode == KGSL_CACHEMODE_WRITETHROUGH))
entry->memdesc.priv |= KGSL_MEMDESC_CAN_RECLAIM;
kgsl_process_add_stats(private,
kgsl_memdesc_usermem_type(&entry->memdesc),
entry->memdesc.size);
trace_kgsl_mem_alloc(entry);
kgsl_mem_entry_commit_process(entry);
return entry;
err:
kfree(entry);
return ERR_PTR(ret);
}
2.2.1 kgsl_mem_entry_create
static struct kgsl_mem_entry *kgsl_mem_entry_create(void)
{
struct kgsl_mem_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (entry != NULL) {
kref_init(&entry->refcount);
kref_get(&entry->refcount);
atomic_set(&entry->map_count, 0);
}
return entry;
}
2.2.2 kgsl_allocate_user
enum kgsl_mmutype {
KGSL_MMU_TYPE_IOMMU = 0,
KGSL_MMU_TYPE_NONE
};
int kgsl_allocate_user(struct kgsl_device *device, struct kgsl_memdesc *memdesc,
u64 size, u64 flags, u32 priv)
{
if (device->mmu.type == KGSL_MMU_TYPE_NONE)
return kgsl_alloc_contiguous(device, memdesc, size, flags,
priv);
else if (flags & KGSL_MEMFLAGS_SECURE)
return kgsl_allocate_secure(device, memdesc, size, flags, priv);
return kgsl_alloc_pages(device, memdesc, size, flags, priv);
}
2.2.3 kgsl_alloc_pages
static int kgsl_alloc_pages(struct kgsl_device *device,
struct kgsl_memdesc *memdesc, u64 size, u64 flags, u32 priv)
{
struct page **pages
int count
// size大小对齐
size = PAGE_ALIGN(size)
// 判断size大小有效性
if (!size || size > UINT_MAX)
return -EINVAL
// 根据标志位初始化kgsl_memdesc[见2.2.4节]
kgsl_memdesc_init(device, memdesc, flags)
// 传入的priv为0
memdesc->priv |= priv
//
if (priv & KGSL_MEMDESC_SYSMEM) {
memdesc->ops = &kgsl_system_ops
count = kgsl_system_alloc_pages(size, &pages, device->dev)
} else {
// 设置kgsl_memdesc的kgsl_memdesc_ops实现[2.2.5节]
memdesc->ops = &kgsl_page_ops
// 分配页面并返回分配的page[2.2.6节]
count = _kgsl_alloc_pages(memdesc, size, &pages, device->dev)
}
if (count < 0)
return count
// 页面指针
memdesc->pages = pages
// 内存大小
memdesc->size = size
// 页面数量
memdesc->page_count = count
// 更新全局的kgsl的内存统计: 将申请的内存大小统计进kgsl_driver的stats结构体page_alloc成员
KGSL_STATS_ADD(size, &kgsl_driver.stats.page_alloc,
&kgsl_driver.stats.page_alloc_max)
return 0
}
2.2.4 kgsl_memdesc_init
void kgsl_memdesc_init(struct kgsl_device *device,
struct kgsl_memdesc *memdesc, uint64_t flags)
{
struct kgsl_mmu *mmu = &device->mmu;
unsigned int align;
memset(memdesc, 0, sizeof(*memdesc));
if (!kgsl_mmu_is_perprocess(mmu))
flags &= ~((uint64_t) KGSL_MEMFLAGS_USE_CPU_MAP);
if (flags & KGSL_MEMFLAGS_SECURE)
flags &= ~((uint64_t) KGSL_MEMFLAGS_USE_CPU_MAP);
if (!kgsl_mmu_has_feature(device, KGSL_MMU_IO_COHERENT)) {
flags &= ~((uint64_t) KGSL_MEMFLAGS_IOCOHERENT);
WARN_ONCE(IS_ENABLED(CONFIG_QCOM_KGSL_IOCOHERENCY_DEFAULT),
"I/O coherency is not supported on this target\n");
} else if (IS_ENABLED(CONFIG_QCOM_KGSL_IOCOHERENCY_DEFAULT))
flags |= KGSL_MEMFLAGS_IOCOHERENT;
if (!kgsl_cachemode_is_cached(flags))
flags &= ~((u64) KGSL_MEMFLAGS_IOCOHERENT);
if (kgsl_mmu_has_feature(device, KGSL_MMU_NEED_GUARD_PAGE) ||
(flags & KGSL_MEMFLAGS_GUARD_PAGE))
memdesc->priv |= KGSL_MEMDESC_GUARD_PAGE;
if (flags & KGSL_MEMFLAGS_SECURE)
memdesc->priv |= KGSL_MEMDESC_SECURE;
memdesc->flags = flags;
memdesc->dev = &device->pdev->dev;
align = max_t(unsigned int,
kgsl_memdesc_get_align(memdesc), ilog2(PAGE_SIZE));
kgsl_memdesc_set_align(memdesc, align);
spin_lock_init(&memdesc->lock);
}
2.2.5 kgsl_page_ops
static const struct kgsl_memdesc_ops kgsl_page_ops = {
.free = kgsl_free_pages,
.vmflags = VM_DONTDUMP | VM_DONTEXPAND | VM_DONTCOPY | VM_MIXEDMAP,
.vmfault = kgsl_paged_vmfault,
.map_kernel = kgsl_paged_map_kernel,
.unmap_kernel = kgsl_paged_unmap_kernel,
.put_gpuaddr = kgsl_unmap_and_put_gpuaddr,
}
2.2.6 _kgsl_alloc_pages
static int _kgsl_alloc_pages(struct kgsl_memdesc *memdesc,
u64 size, struct page ***pages, struct device *dev)
{
int count = 0
// 将内存大小转换为页面数量
int npages = size >> PAGE_SHIFT
// attempt to allocate physically contiguous memory by kmalloc
// but upon failure, fall back to non-contiguous (vmalloc) allocation
struct page **local = kvcalloc(npages, sizeof(*local), GFP_KERNEL)
u32 page_size, align
u64 len = size
if (!local)
return -ENOMEM
// 共享内存设置成功或者未配置CONFIG_QCOM_KGSL_USE_SHMEM则返回0[见2.2.6.1节]
count = kgsl_memdesc_file_setup(memdesc, size)
if (count) {
kvfree(local)
return count
}
/* Start with 1MB alignment to get the biggest page we can */
align = ilog2(SZ_1M)
// 根据内存大小计算页面大小
page_size = kgsl_get_page_size(len, align)
while (len) {
// 调用kgsl_pool_alloc_page分配, 并将获取的page通过local数组返回
int ret = kgsl_alloc_page(&page_size, &local[count],
npages, &align, count, memdesc->shmem_filp, dev)
if (ret == -EAGAIN)
continue
else if (ret <= 0) {
int i
for (i = 0
int n = 1 << compound_order(local[i])
kgsl_free_page(local[i])
i += n
}
kvfree(local)
if (!kgsl_sharedmem_noretry_flag)
pr_err_ratelimited("kgsl: out of memory: only allocated %lldKb of %lldKb requested\n",
(size - len) >> 10, size >> 10)
if (memdesc->shmem_filp)
fput(memdesc->shmem_filp)
return -ENOMEM
}
count += ret
npages -= ret
len -= page_size
page_size = kgsl_get_page_size(len, align)
}
// pages作为返回值
*pages = local
return count
}
2.2.6.1 kgsl_memdesc_file_setup
#ifdef CONFIG_QCOM_KGSL_USE_SHMEM
static int kgsl_memdesc_file_setup(struct kgsl_memdesc *memdesc, uint64_t size)
{
int ret;
memdesc->shmem_filp = shmem_file_setup("kgsl-3d0", size,
VM_NORESERVE);
if (IS_ERR(memdesc->shmem_filp)) {
ret = PTR_ERR(memdesc->shmem_filp);
pr_err("kgsl: unable to setup shmem file err %d\n",
ret);
memdesc->shmem_filp = NULL;
return ret;
}
return 0;
}
#else
static int kgsl_memdesc_file_setup(struct kgsl_memdesc *memdesc, uint64_t size)
{
return 0;
}
#endif
2.2.7 kgsl_mem_entry_attach_and_map
static int kgsl_mem_entry_attach_and_map(struct kgsl_device *device,
struct kgsl_process_private *process,
struct kgsl_mem_entry *entry)
{
struct kgsl_memdesc *memdesc = &entry->memdesc;
int ret;
ret = kgsl_mem_entry_attach_to_process(device, process, entry);
if (ret)
return ret;
if (memdesc->gpuaddr) {
ret = kgsl_mmu_map(memdesc->pagetable, memdesc);
if (ret) {
kgsl_mem_entry_detach_process(entry);
return ret;
}
}
kgsl_memfree_purge(memdesc->pagetable, memdesc->gpuaddr,
memdesc->size);
return ret;
}
2.2.7.1 kgsl_mem_entry_attach_to_process
static int kgsl_mem_entry_attach_to_process(struct kgsl_device *device,
struct kgsl_process_private *process,
struct kgsl_mem_entry *entry)
{
struct kgsl_memdesc *memdesc = &entry->memdesc;
int ret, id;
ret = kgsl_process_private_get(process);
if (!ret)
return -EBADF;
if (!kgsl_memdesc_use_cpu_map(memdesc) &&
kgsl_mmu_get_mmutype(device) != KGSL_MMU_TYPE_NONE) {
struct kgsl_pagetable *pagetable;
pagetable = kgsl_memdesc_is_secured(memdesc) ?
device->mmu.securepagetable : process->pagetable;
ret = kgsl_mmu_get_gpuaddr(pagetable, memdesc);
if (ret) {
kgsl_process_private_put(process);
return ret;
}
}
idr_preload(GFP_KERNEL);
spin_lock(&process->mem_lock);
id = idr_alloc(&process->mem_idr, NULL, 1, 0, GFP_NOWAIT);
spin_unlock(&process->mem_lock);
idr_preload_end();
if (id < 0) {
if (!kgsl_memdesc_use_cpu_map(memdesc))
kgsl_mmu_put_gpuaddr(memdesc->pagetable, memdesc);
kgsl_process_private_put(process);
return id;
}
entry->id = id;
entry->priv = process;
return 0;
}
2.2.7.2 kgsl_memdesc_use_cpu_map
#define KGSL_MEMFLAGS_USE_CPU_MAP (1ULL << 28)
#define KGSL_MEMFLAGS_SPARSE_PHYS (1ULL << 29)
#define KGSL_MEMFLAGS_SPARSE_VIRT (1ULL << 30)
#define KGSL_MEMFLAGS_IOCOHERENT (1ULL << 31)
#define KGSL_MEMFLAGS_GUARD_PAGE (1ULL << 33)
#define KGSL_MEMFLAGS_VBO (1ULL << 34)
static inline bool
kgsl_memdesc_use_cpu_map(const struct kgsl_memdesc *memdesc)
{
return memdesc && (memdesc->flags & KGSL_MEMFLAGS_USE_CPU_MAP);
}
2.2.7.3 kgsl_pagetable
struct kgsl_pagetable {
spinlock_t lock;
struct kref refcount;
struct list_head list;
unsigned int name;
struct kobject *kobj;
struct work_struct destroy_ws;
struct {
atomic_t entries;
atomic_long_t mapped;
atomic_long_t max_mapped;
} stats;
const struct kgsl_mmu_pt_ops *pt_ops;
uint64_t fault_addr;
struct kgsl_mmu *mmu;
struct rb_root rbtree;
u64 va_start;
u64 va_end;
u64 svm_start;
u64 svm_end;
u64 compat_va_start;
u64 compat_va_end;
u64 global_base;
};
2.2.7.4 kgsl_mmu_get_gpuaddr
#define PT_OP_VALID(_pt, _field) \
(((_pt) != NULL) && \
((_pt)->pt_ops != NULL) && \
((_pt)->pt_ops->_field != NULL))
static inline int kgsl_mmu_get_gpuaddr(struct kgsl_pagetable *pagetable,
struct kgsl_memdesc *memdesc)
{
if (PT_OP_VALID(pagetable, get_gpuaddr))
return pagetable->pt_ops->get_gpuaddr(pagetable, memdesc);
return -ENOMEM;
}
2.2.7.5 kgsl_iommu_get_gpuaddr
static int kgsl_iommu_get_gpuaddr(struct kgsl_pagetable *pagetable,
struct kgsl_memdesc *memdesc)
{
int ret = 0
uint64_t addr, start, end, size
unsigned int align
if (WARN_ON(kgsl_memdesc_use_cpu_map(memdesc)))
return -EINVAL
if (memdesc->flags & KGSL_MEMFLAGS_SECURE &&
pagetable->name != KGSL_MMU_SECURE_PT)
return -EINVAL
// 获取映射区域(kgsl_memdesc)的大小
size = kgsl_memdesc_footprint(memdesc)
align = max_t(uint64_t, 1 << kgsl_memdesc_get_align(memdesc),
PAGE_SIZE)
if (memdesc->flags & KGSL_MEMFLAGS_FORCE_32BIT) {
start = pagetable->compat_va_start
end = pagetable->compat_va_end
} else {
// Start of virtual range used in this pagetable
start = pagetable->va_start
// End of virtual range
end = pagetable->va_end
}
spin_lock(&pagetable->lock)
// 获取一块未映射的虚拟地址[2.2.7.6节]
addr = _get_unmapped_area(pagetable, start, end, size, align)
if (addr == (uint64_t) -ENOMEM) {
ret = -ENOMEM
goto out
}
/*
* This path is only called in a non-SVM path with locks so we can be
* sure we aren't racing with anybody so we don't need to worry about
* taking the lock
*/
// 将虚拟地址插入页表[2.2.7.7节]
ret = _insert_gpuaddr(pagetable, addr, size)
if (ret == 0) {
// 设置GPU虚拟地址
memdesc->gpuaddr = addr
// 设置页表
memdesc->pagetable = pagetable
}
out:
spin_unlock(&pagetable->lock)
return ret
}
2.2.7.6 _get_unmapped_area
struct kgsl_iommu_addr_entry {
uint64_t base;
uint64_t size;
struct rb_node node;
};
static uint64_t _get_unmapped_area(struct kgsl_pagetable *pagetable,
uint64_t bottom, uint64_t top, uint64_t size,
uint64_t align)
{
struct rb_node *node = rb_first(&pagetable->rbtree);
uint64_t start;
bottom = ALIGN(bottom, align);
start = bottom;
while (node != NULL) {
uint64_t gap;
struct kgsl_iommu_addr_entry *entry = rb_entry(node,
struct kgsl_iommu_addr_entry, node);
if (entry->base < bottom) {
if (entry->base + entry->size > bottom)
start = ALIGN(entry->base + entry->size, align);
node = rb_next(node);
continue;
}
if (entry->base >= top)
break;
if (start < entry->base) {
gap = entry->base - start;
if (gap >= size)
return start;
}
if (entry->base + entry->size >= top)
return (uint64_t) -ENOMEM;
start = ALIGN(entry->base + entry->size, align);
node = rb_next(node);
}
if (start + size <= top)
return start;
return (uint64_t) -ENOMEM;
}
2.2.7.7 _insert_gpuaddr
static int _insert_gpuaddr(struct kgsl_pagetable *pagetable,
uint64_t gpuaddr, uint64_t size)
{
struct rb_node **node, *parent = NULL;
struct kgsl_iommu_addr_entry *new =
kmem_cache_alloc(addr_entry_cache, GFP_ATOMIC);
if (new == NULL)
return -ENOMEM;
new->base = gpuaddr;
new->size = size;
node = &pagetable->rbtree.rb_node;
while (*node != NULL) {
struct kgsl_iommu_addr_entry *this;
parent = *node;
this = rb_entry(parent, struct kgsl_iommu_addr_entry, node);
if (new->base < this->base)
node = &parent->rb_left;
else if (new->base > this->base)
node = &parent->rb_right;
else {
WARN(1, "duplicate gpuaddr: 0x%llx\n", gpuaddr);
kmem_cache_free(addr_entry_cache, new);
return -EEXIST;
}
}
rb_link_node(&new->node, parent, node);
rb_insert_color(&new->node, &pagetable->rbtree);
return 0;
}