ctvmem.c revision 514eef9c2a711b4c24b97bb456d39695a6fe1775
1/**
2 * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
3 *
4 * This source file is released under GPL v2 license (no other versions).
5 * See the COPYING file included in the main directory of this source
6 * distribution for the license terms and conditions.
7 *
8 * @File    ctvmem.c
9 *
10 * @Brief
11 * This file contains the implementation of virtual memory management object
12 * for card device.
13 *
14 * @Author Liu Chun
15 * @Date Apr 1 2008
16 */
17
18#include "ctvmem.h"
19#include <linux/slab.h>
20#include <linux/mm.h>
21#include <linux/io.h>
22#include <sound/pcm.h>
23
24#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *))
25#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE)
26
27/* *
28 * Find or create vm block based on requested @size.
29 * @size must be page aligned.
30 * */
31static struct ct_vm_block *
32get_vm_block(struct ct_vm *vm, unsigned int size)
33{
34	struct ct_vm_block *block = NULL, *entry;
35	struct list_head *pos;
36
37	size = CT_PAGE_ALIGN(size);
38	if (size > vm->size) {
39		printk(KERN_ERR "ctxfi: Fail! No sufficient device virtural "
40				  "memory space available!\n");
41		return NULL;
42	}
43
44	mutex_lock(&vm->lock);
45	list_for_each(pos, &vm->unused) {
46		entry = list_entry(pos, struct ct_vm_block, list);
47		if (entry->size >= size)
48			break; /* found a block that is big enough */
49	}
50	if (pos == &vm->unused)
51		goto out;
52
53	if (entry->size == size) {
54		/* Move the vm node from unused list to used list directly */
55		list_del(&entry->list);
56		list_add(&entry->list, &vm->used);
57		vm->size -= size;
58		block = entry;
59		goto out;
60	}
61
62	block = kzalloc(sizeof(*block), GFP_KERNEL);
63	if (NULL == block)
64		goto out;
65
66	block->addr = entry->addr;
67	block->size = size;
68	list_add(&block->list, &vm->used);
69	entry->addr += size;
70	entry->size -= size;
71	vm->size -= size;
72
73 out:
74	mutex_unlock(&vm->lock);
75	return block;
76}
77
78static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block)
79{
80	struct ct_vm_block *entry, *pre_ent;
81	struct list_head *pos, *pre;
82
83	block->size = CT_PAGE_ALIGN(block->size);
84
85	mutex_lock(&vm->lock);
86	list_del(&block->list);
87	vm->size += block->size;
88
89	list_for_each(pos, &vm->unused) {
90		entry = list_entry(pos, struct ct_vm_block, list);
91		if (entry->addr >= (block->addr + block->size))
92			break; /* found a position */
93	}
94	if (pos == &vm->unused) {
95		list_add_tail(&block->list, &vm->unused);
96		entry = block;
97	} else {
98		if ((block->addr + block->size) == entry->addr) {
99			entry->addr = block->addr;
100			entry->size += block->size;
101			kfree(block);
102		} else {
103			__list_add(&block->list, pos->prev, pos);
104			entry = block;
105		}
106	}
107
108	pos = &entry->list;
109	pre = pos->prev;
110	while (pre != &vm->unused) {
111		entry = list_entry(pos, struct ct_vm_block, list);
112		pre_ent = list_entry(pre, struct ct_vm_block, list);
113		if ((pre_ent->addr + pre_ent->size) > entry->addr)
114			break;
115
116		pre_ent->size += entry->size;
117		list_del(pos);
118		kfree(entry);
119		pos = pre;
120		pre = pos->prev;
121	}
122	mutex_unlock(&vm->lock);
123}
124
125/* Map host addr (kmalloced/vmalloced) to device logical addr. */
126static struct ct_vm_block *
127ct_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size)
128{
129	struct ct_vm_block *block;
130	unsigned int pte_start;
131	unsigned i, pages;
132	unsigned long *ptp;
133
134	block = get_vm_block(vm, size);
135	if (block == NULL) {
136		printk(KERN_ERR "ctxfi: No virtual memory block that is big "
137				  "enough to allocate!\n");
138		return NULL;
139	}
140
141	ptp = vm->ptp[0];
142	pte_start = (block->addr >> CT_PAGE_SHIFT);
143	pages = block->size >> CT_PAGE_SHIFT;
144	for (i = 0; i < pages; i++) {
145		unsigned long addr;
146		addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT);
147		ptp[pte_start + i] = addr;
148	}
149
150	block->size = size;
151	return block;
152}
153
154static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block)
155{
156	/* do unmapping */
157	put_vm_block(vm, block);
158}
159
160/* *
161 * return the host (kmalloced) addr of the @index-th device
162 * page talbe page on success, or NULL on failure.
163 * The first returned NULL indicates the termination.
164 * */
165static void *
166ct_get_ptp_virt(struct ct_vm *vm, int index)
167{
168	void *addr;
169
170	addr = (index >= CT_PTP_NUM) ? NULL : vm->ptp[index];
171
172	return addr;
173}
174
175int ct_vm_create(struct ct_vm **rvm)
176{
177	struct ct_vm *vm;
178	struct ct_vm_block *block;
179	int i;
180
181	*rvm = NULL;
182
183	vm = kzalloc(sizeof(*vm), GFP_KERNEL);
184	if (NULL == vm)
185		return -ENOMEM;
186
187	mutex_init(&vm->lock);
188
189	/* Allocate page table pages */
190	for (i = 0; i < CT_PTP_NUM; i++) {
191		vm->ptp[i] = kmalloc(PAGE_SIZE, GFP_KERNEL);
192		if (NULL == vm->ptp[i])
193			break;
194	}
195	if (!i) {
196		/* no page table pages are allocated */
197		kfree(vm);
198		return -ENOMEM;
199	}
200	vm->size = CT_ADDRS_PER_PAGE * i;
201	/* Initialise remaining ptps */
202	for (; i < CT_PTP_NUM; i++)
203		vm->ptp[i] = NULL;
204
205	vm->map = ct_vm_map;
206	vm->unmap = ct_vm_unmap;
207	vm->get_ptp_virt = ct_get_ptp_virt;
208	INIT_LIST_HEAD(&vm->unused);
209	INIT_LIST_HEAD(&vm->used);
210	block = kzalloc(sizeof(*block), GFP_KERNEL);
211	if (NULL != block) {
212		block->addr = 0;
213		block->size = vm->size;
214		list_add(&block->list, &vm->unused);
215	}
216
217	*rvm = vm;
218	return 0;
219}
220
221/* The caller must ensure no mapping pages are being used
222 * by hardware before calling this function */
223void ct_vm_destroy(struct ct_vm *vm)
224{
225	int i;
226	struct list_head *pos;
227	struct ct_vm_block *entry;
228
229	/* free used and unused list nodes */
230	while (!list_empty(&vm->used)) {
231		pos = vm->used.next;
232		list_del(pos);
233		entry = list_entry(pos, struct ct_vm_block, list);
234		kfree(entry);
235	}
236	while (!list_empty(&vm->unused)) {
237		pos = vm->unused.next;
238		list_del(pos);
239		entry = list_entry(pos, struct ct_vm_block, list);
240		kfree(entry);
241	}
242
243	/* free allocated page table pages */
244	for (i = 0; i < CT_PTP_NUM; i++)
245		kfree(vm->ptp[i]);
246
247	vm->size = 0;
248
249	kfree(vm);
250}
251