162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * @File ctvmem.c 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * @Brief 862306a36Sopenharmony_ci * This file contains the implementation of virtual memory management object 962306a36Sopenharmony_ci * for card device. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * @Author Liu Chun 1262306a36Sopenharmony_ci * @Date Apr 1 2008 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "ctvmem.h" 1662306a36Sopenharmony_ci#include "ctatc.h" 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/mm.h> 1962306a36Sopenharmony_ci#include <linux/io.h> 2062306a36Sopenharmony_ci#include <sound/pcm.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *)) 2362306a36Sopenharmony_ci#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* * 2662306a36Sopenharmony_ci * Find or create vm block based on requested @size. 2762306a36Sopenharmony_ci * @size must be page aligned. 2862306a36Sopenharmony_ci * */ 2962306a36Sopenharmony_cistatic struct ct_vm_block * 3062306a36Sopenharmony_ciget_vm_block(struct ct_vm *vm, unsigned int size, struct ct_atc *atc) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci struct ct_vm_block *block = NULL, *entry; 3362306a36Sopenharmony_ci struct list_head *pos; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci size = CT_PAGE_ALIGN(size); 3662306a36Sopenharmony_ci if (size > vm->size) { 3762306a36Sopenharmony_ci dev_err(atc->card->dev, 3862306a36Sopenharmony_ci "Fail! No sufficient device virtual memory space available!\n"); 3962306a36Sopenharmony_ci return NULL; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci mutex_lock(&vm->lock); 4362306a36Sopenharmony_ci list_for_each(pos, &vm->unused) { 4462306a36Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 4562306a36Sopenharmony_ci if (entry->size >= size) 4662306a36Sopenharmony_ci break; /* found a block that is big enough */ 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci if (pos == &vm->unused) 4962306a36Sopenharmony_ci goto out; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (entry->size == size) { 5262306a36Sopenharmony_ci /* Move the vm node from unused list to used list directly */ 5362306a36Sopenharmony_ci list_move(&entry->list, &vm->used); 5462306a36Sopenharmony_ci vm->size -= size; 5562306a36Sopenharmony_ci block = entry; 5662306a36Sopenharmony_ci goto out; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci block = kzalloc(sizeof(*block), GFP_KERNEL); 6062306a36Sopenharmony_ci if (!block) 6162306a36Sopenharmony_ci goto out; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci block->addr = entry->addr; 6462306a36Sopenharmony_ci block->size = size; 6562306a36Sopenharmony_ci list_add(&block->list, &vm->used); 6662306a36Sopenharmony_ci entry->addr += size; 6762306a36Sopenharmony_ci entry->size -= size; 6862306a36Sopenharmony_ci vm->size -= size; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci out: 7162306a36Sopenharmony_ci mutex_unlock(&vm->lock); 7262306a36Sopenharmony_ci return block; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct ct_vm_block *entry, *pre_ent; 7862306a36Sopenharmony_ci struct list_head *pos, *pre; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci block->size = CT_PAGE_ALIGN(block->size); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci mutex_lock(&vm->lock); 8362306a36Sopenharmony_ci list_del(&block->list); 8462306a36Sopenharmony_ci vm->size += block->size; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci list_for_each(pos, &vm->unused) { 8762306a36Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 8862306a36Sopenharmony_ci if (entry->addr >= (block->addr + block->size)) 8962306a36Sopenharmony_ci break; /* found a position */ 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci if (pos == &vm->unused) { 9262306a36Sopenharmony_ci list_add_tail(&block->list, &vm->unused); 9362306a36Sopenharmony_ci entry = block; 9462306a36Sopenharmony_ci } else { 9562306a36Sopenharmony_ci if ((block->addr + block->size) == entry->addr) { 9662306a36Sopenharmony_ci entry->addr = block->addr; 9762306a36Sopenharmony_ci entry->size += block->size; 9862306a36Sopenharmony_ci kfree(block); 9962306a36Sopenharmony_ci } else { 10062306a36Sopenharmony_ci __list_add(&block->list, pos->prev, pos); 10162306a36Sopenharmony_ci entry = block; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci pos = &entry->list; 10662306a36Sopenharmony_ci pre = pos->prev; 10762306a36Sopenharmony_ci while (pre != &vm->unused) { 10862306a36Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 10962306a36Sopenharmony_ci pre_ent = list_entry(pre, struct ct_vm_block, list); 11062306a36Sopenharmony_ci if ((pre_ent->addr + pre_ent->size) > entry->addr) 11162306a36Sopenharmony_ci break; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci pre_ent->size += entry->size; 11462306a36Sopenharmony_ci list_del(pos); 11562306a36Sopenharmony_ci kfree(entry); 11662306a36Sopenharmony_ci pos = pre; 11762306a36Sopenharmony_ci pre = pos->prev; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci mutex_unlock(&vm->lock); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/* Map host addr (kmalloced/vmalloced) to device logical addr. */ 12362306a36Sopenharmony_cistatic struct ct_vm_block * 12462306a36Sopenharmony_cict_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct ct_vm_block *block; 12762306a36Sopenharmony_ci unsigned int pte_start; 12862306a36Sopenharmony_ci unsigned i, pages; 12962306a36Sopenharmony_ci unsigned long *ptp; 13062306a36Sopenharmony_ci struct ct_atc *atc = snd_pcm_substream_chip(substream); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci block = get_vm_block(vm, size, atc); 13362306a36Sopenharmony_ci if (block == NULL) { 13462306a36Sopenharmony_ci dev_err(atc->card->dev, 13562306a36Sopenharmony_ci "No virtual memory block that is big enough to allocate!\n"); 13662306a36Sopenharmony_ci return NULL; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci ptp = (unsigned long *)vm->ptp[0].area; 14062306a36Sopenharmony_ci pte_start = (block->addr >> CT_PAGE_SHIFT); 14162306a36Sopenharmony_ci pages = block->size >> CT_PAGE_SHIFT; 14262306a36Sopenharmony_ci for (i = 0; i < pages; i++) { 14362306a36Sopenharmony_ci unsigned long addr; 14462306a36Sopenharmony_ci addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT); 14562306a36Sopenharmony_ci ptp[pte_start + i] = addr; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci block->size = size; 14962306a36Sopenharmony_ci return block; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci /* do unmapping */ 15562306a36Sopenharmony_ci put_vm_block(vm, block); 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/* * 15962306a36Sopenharmony_ci * return the host physical addr of the @index-th device 16062306a36Sopenharmony_ci * page table page on success, or ~0UL on failure. 16162306a36Sopenharmony_ci * The first returned ~0UL indicates the termination. 16262306a36Sopenharmony_ci * */ 16362306a36Sopenharmony_cistatic dma_addr_t 16462306a36Sopenharmony_cict_get_ptp_phys(struct ct_vm *vm, int index) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci return (index >= CT_PTP_NUM) ? ~0UL : vm->ptp[index].addr; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ciint ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci struct ct_vm *vm; 17262306a36Sopenharmony_ci struct ct_vm_block *block; 17362306a36Sopenharmony_ci int i, err = 0; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci *rvm = NULL; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci vm = kzalloc(sizeof(*vm), GFP_KERNEL); 17862306a36Sopenharmony_ci if (!vm) 17962306a36Sopenharmony_ci return -ENOMEM; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci mutex_init(&vm->lock); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci /* Allocate page table pages */ 18462306a36Sopenharmony_ci for (i = 0; i < CT_PTP_NUM; i++) { 18562306a36Sopenharmony_ci err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, 18662306a36Sopenharmony_ci &pci->dev, 18762306a36Sopenharmony_ci PAGE_SIZE, &vm->ptp[i]); 18862306a36Sopenharmony_ci if (err < 0) 18962306a36Sopenharmony_ci break; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci if (err < 0) { 19262306a36Sopenharmony_ci /* no page table pages are allocated */ 19362306a36Sopenharmony_ci ct_vm_destroy(vm); 19462306a36Sopenharmony_ci return -ENOMEM; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci vm->size = CT_ADDRS_PER_PAGE * i; 19762306a36Sopenharmony_ci vm->map = ct_vm_map; 19862306a36Sopenharmony_ci vm->unmap = ct_vm_unmap; 19962306a36Sopenharmony_ci vm->get_ptp_phys = ct_get_ptp_phys; 20062306a36Sopenharmony_ci INIT_LIST_HEAD(&vm->unused); 20162306a36Sopenharmony_ci INIT_LIST_HEAD(&vm->used); 20262306a36Sopenharmony_ci block = kzalloc(sizeof(*block), GFP_KERNEL); 20362306a36Sopenharmony_ci if (NULL != block) { 20462306a36Sopenharmony_ci block->addr = 0; 20562306a36Sopenharmony_ci block->size = vm->size; 20662306a36Sopenharmony_ci list_add(&block->list, &vm->unused); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci *rvm = vm; 21062306a36Sopenharmony_ci return 0; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci/* The caller must ensure no mapping pages are being used 21462306a36Sopenharmony_ci * by hardware before calling this function */ 21562306a36Sopenharmony_civoid ct_vm_destroy(struct ct_vm *vm) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci int i; 21862306a36Sopenharmony_ci struct list_head *pos; 21962306a36Sopenharmony_ci struct ct_vm_block *entry; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* free used and unused list nodes */ 22262306a36Sopenharmony_ci while (!list_empty(&vm->used)) { 22362306a36Sopenharmony_ci pos = vm->used.next; 22462306a36Sopenharmony_ci list_del(pos); 22562306a36Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 22662306a36Sopenharmony_ci kfree(entry); 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci while (!list_empty(&vm->unused)) { 22962306a36Sopenharmony_ci pos = vm->unused.next; 23062306a36Sopenharmony_ci list_del(pos); 23162306a36Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 23262306a36Sopenharmony_ci kfree(entry); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* free allocated page table pages */ 23662306a36Sopenharmony_ci for (i = 0; i < CT_PTP_NUM; i++) 23762306a36Sopenharmony_ci snd_dma_free_pages(&vm->ptp[i]); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci vm->size = 0; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci kfree(vm); 24262306a36Sopenharmony_ci} 243