18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/** 38c2ecf20Sopenharmony_ci * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * @File ctvmem.c 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * @Brief 88c2ecf20Sopenharmony_ci * This file contains the implementation of virtual memory management object 98c2ecf20Sopenharmony_ci * for card device. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * @Author Liu Chun 128c2ecf20Sopenharmony_ci * @Date Apr 1 2008 138c2ecf20Sopenharmony_ci */ 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include "ctvmem.h" 168c2ecf20Sopenharmony_ci#include "ctatc.h" 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/mm.h> 198c2ecf20Sopenharmony_ci#include <linux/io.h> 208c2ecf20Sopenharmony_ci#include <sound/pcm.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *)) 238c2ecf20Sopenharmony_ci#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE) 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* * 268c2ecf20Sopenharmony_ci * Find or create vm block based on requested @size. 278c2ecf20Sopenharmony_ci * @size must be page aligned. 288c2ecf20Sopenharmony_ci * */ 298c2ecf20Sopenharmony_cistatic struct ct_vm_block * 308c2ecf20Sopenharmony_ciget_vm_block(struct ct_vm *vm, unsigned int size, struct ct_atc *atc) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci struct ct_vm_block *block = NULL, *entry; 338c2ecf20Sopenharmony_ci struct list_head *pos; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci size = CT_PAGE_ALIGN(size); 368c2ecf20Sopenharmony_ci if (size > vm->size) { 378c2ecf20Sopenharmony_ci dev_err(atc->card->dev, 388c2ecf20Sopenharmony_ci "Fail! No sufficient device virtual memory space available!\n"); 398c2ecf20Sopenharmony_ci return NULL; 408c2ecf20Sopenharmony_ci } 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci mutex_lock(&vm->lock); 438c2ecf20Sopenharmony_ci list_for_each(pos, &vm->unused) { 448c2ecf20Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 458c2ecf20Sopenharmony_ci if (entry->size >= size) 468c2ecf20Sopenharmony_ci break; /* found a block that is big enough */ 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci if (pos == &vm->unused) 498c2ecf20Sopenharmony_ci goto out; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (entry->size == size) { 528c2ecf20Sopenharmony_ci /* Move the vm node from unused list to used list directly */ 538c2ecf20Sopenharmony_ci list_move(&entry->list, &vm->used); 548c2ecf20Sopenharmony_ci vm->size -= size; 558c2ecf20Sopenharmony_ci block = entry; 568c2ecf20Sopenharmony_ci goto out; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci block = kzalloc(sizeof(*block), GFP_KERNEL); 608c2ecf20Sopenharmony_ci if (!block) 618c2ecf20Sopenharmony_ci goto out; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci block->addr = entry->addr; 648c2ecf20Sopenharmony_ci block->size = size; 658c2ecf20Sopenharmony_ci list_add(&block->list, &vm->used); 668c2ecf20Sopenharmony_ci entry->addr += size; 678c2ecf20Sopenharmony_ci entry->size -= size; 688c2ecf20Sopenharmony_ci vm->size -= size; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci out: 718c2ecf20Sopenharmony_ci mutex_unlock(&vm->lock); 728c2ecf20Sopenharmony_ci return block; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci struct ct_vm_block *entry, *pre_ent; 788c2ecf20Sopenharmony_ci struct list_head *pos, *pre; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci block->size = CT_PAGE_ALIGN(block->size); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci mutex_lock(&vm->lock); 838c2ecf20Sopenharmony_ci list_del(&block->list); 848c2ecf20Sopenharmony_ci vm->size += block->size; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci list_for_each(pos, &vm->unused) { 878c2ecf20Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 888c2ecf20Sopenharmony_ci if (entry->addr >= (block->addr + block->size)) 898c2ecf20Sopenharmony_ci break; /* found a position */ 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci if (pos == &vm->unused) { 928c2ecf20Sopenharmony_ci list_add_tail(&block->list, &vm->unused); 938c2ecf20Sopenharmony_ci entry = block; 948c2ecf20Sopenharmony_ci } else { 958c2ecf20Sopenharmony_ci if ((block->addr + block->size) == entry->addr) { 968c2ecf20Sopenharmony_ci entry->addr = block->addr; 978c2ecf20Sopenharmony_ci entry->size += block->size; 988c2ecf20Sopenharmony_ci kfree(block); 998c2ecf20Sopenharmony_ci } else { 1008c2ecf20Sopenharmony_ci __list_add(&block->list, pos->prev, pos); 1018c2ecf20Sopenharmony_ci entry = block; 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci pos = &entry->list; 1068c2ecf20Sopenharmony_ci pre = pos->prev; 1078c2ecf20Sopenharmony_ci while (pre != &vm->unused) { 1088c2ecf20Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 1098c2ecf20Sopenharmony_ci pre_ent = list_entry(pre, struct ct_vm_block, list); 1108c2ecf20Sopenharmony_ci if ((pre_ent->addr + pre_ent->size) > entry->addr) 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci pre_ent->size += entry->size; 1148c2ecf20Sopenharmony_ci list_del(pos); 1158c2ecf20Sopenharmony_ci kfree(entry); 1168c2ecf20Sopenharmony_ci pos = pre; 1178c2ecf20Sopenharmony_ci pre = pos->prev; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci mutex_unlock(&vm->lock); 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci/* Map host addr (kmalloced/vmalloced) to device logical addr. */ 1238c2ecf20Sopenharmony_cistatic struct ct_vm_block * 1248c2ecf20Sopenharmony_cict_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct ct_vm_block *block; 1278c2ecf20Sopenharmony_ci unsigned int pte_start; 1288c2ecf20Sopenharmony_ci unsigned i, pages; 1298c2ecf20Sopenharmony_ci unsigned long *ptp; 1308c2ecf20Sopenharmony_ci struct ct_atc *atc = snd_pcm_substream_chip(substream); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci block = get_vm_block(vm, size, atc); 1338c2ecf20Sopenharmony_ci if (block == NULL) { 1348c2ecf20Sopenharmony_ci dev_err(atc->card->dev, 1358c2ecf20Sopenharmony_ci "No virtual memory block that is big enough to allocate!\n"); 1368c2ecf20Sopenharmony_ci return NULL; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci ptp = (unsigned long *)vm->ptp[0].area; 1408c2ecf20Sopenharmony_ci pte_start = (block->addr >> CT_PAGE_SHIFT); 1418c2ecf20Sopenharmony_ci pages = block->size >> CT_PAGE_SHIFT; 1428c2ecf20Sopenharmony_ci for (i = 0; i < pages; i++) { 1438c2ecf20Sopenharmony_ci unsigned long addr; 1448c2ecf20Sopenharmony_ci addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT); 1458c2ecf20Sopenharmony_ci ptp[pte_start + i] = addr; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci block->size = size; 1498c2ecf20Sopenharmony_ci return block; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci /* do unmapping */ 1558c2ecf20Sopenharmony_ci put_vm_block(vm, block); 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci/* * 1598c2ecf20Sopenharmony_ci * return the host physical addr of the @index-th device 1608c2ecf20Sopenharmony_ci * page table page on success, or ~0UL on failure. 1618c2ecf20Sopenharmony_ci * The first returned ~0UL indicates the termination. 1628c2ecf20Sopenharmony_ci * */ 1638c2ecf20Sopenharmony_cistatic dma_addr_t 1648c2ecf20Sopenharmony_cict_get_ptp_phys(struct ct_vm *vm, int index) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci return (index >= CT_PTP_NUM) ? ~0UL : vm->ptp[index].addr; 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ciint ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct ct_vm *vm; 1728c2ecf20Sopenharmony_ci struct ct_vm_block *block; 1738c2ecf20Sopenharmony_ci int i, err = 0; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci *rvm = NULL; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci vm = kzalloc(sizeof(*vm), GFP_KERNEL); 1788c2ecf20Sopenharmony_ci if (!vm) 1798c2ecf20Sopenharmony_ci return -ENOMEM; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci mutex_init(&vm->lock); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci /* Allocate page table pages */ 1848c2ecf20Sopenharmony_ci for (i = 0; i < CT_PTP_NUM; i++) { 1858c2ecf20Sopenharmony_ci err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, 1868c2ecf20Sopenharmony_ci &pci->dev, 1878c2ecf20Sopenharmony_ci PAGE_SIZE, &vm->ptp[i]); 1888c2ecf20Sopenharmony_ci if (err < 0) 1898c2ecf20Sopenharmony_ci break; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci if (err < 0) { 1928c2ecf20Sopenharmony_ci /* no page table pages are allocated */ 1938c2ecf20Sopenharmony_ci ct_vm_destroy(vm); 1948c2ecf20Sopenharmony_ci return -ENOMEM; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci vm->size = CT_ADDRS_PER_PAGE * i; 1978c2ecf20Sopenharmony_ci vm->map = ct_vm_map; 1988c2ecf20Sopenharmony_ci vm->unmap = ct_vm_unmap; 1998c2ecf20Sopenharmony_ci vm->get_ptp_phys = ct_get_ptp_phys; 2008c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vm->unused); 2018c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vm->used); 2028c2ecf20Sopenharmony_ci block = kzalloc(sizeof(*block), GFP_KERNEL); 2038c2ecf20Sopenharmony_ci if (NULL != block) { 2048c2ecf20Sopenharmony_ci block->addr = 0; 2058c2ecf20Sopenharmony_ci block->size = vm->size; 2068c2ecf20Sopenharmony_ci list_add(&block->list, &vm->unused); 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci *rvm = vm; 2108c2ecf20Sopenharmony_ci return 0; 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci/* The caller must ensure no mapping pages are being used 2148c2ecf20Sopenharmony_ci * by hardware before calling this function */ 2158c2ecf20Sopenharmony_civoid ct_vm_destroy(struct ct_vm *vm) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci int i; 2188c2ecf20Sopenharmony_ci struct list_head *pos; 2198c2ecf20Sopenharmony_ci struct ct_vm_block *entry; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* free used and unused list nodes */ 2228c2ecf20Sopenharmony_ci while (!list_empty(&vm->used)) { 2238c2ecf20Sopenharmony_ci pos = vm->used.next; 2248c2ecf20Sopenharmony_ci list_del(pos); 2258c2ecf20Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 2268c2ecf20Sopenharmony_ci kfree(entry); 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci while (!list_empty(&vm->unused)) { 2298c2ecf20Sopenharmony_ci pos = vm->unused.next; 2308c2ecf20Sopenharmony_ci list_del(pos); 2318c2ecf20Sopenharmony_ci entry = list_entry(pos, struct ct_vm_block, list); 2328c2ecf20Sopenharmony_ci kfree(entry); 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* free allocated page table pages */ 2368c2ecf20Sopenharmony_ci for (i = 0; i < CT_PTP_NUM; i++) 2378c2ecf20Sopenharmony_ci snd_dma_free_pages(&vm->ptp[i]); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci vm->size = 0; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci kfree(vm); 2428c2ecf20Sopenharmony_ci} 243