diff options
Diffstat (limited to 'sound/pci/ctxfi/ctvmem.c')
-rw-r--r-- | sound/pci/ctxfi/ctvmem.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/sound/pci/ctxfi/ctvmem.c b/sound/pci/ctxfi/ctvmem.c new file mode 100644 index 000000000000..67665a7e43c6 --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.c | |||
@@ -0,0 +1,250 @@ | |||
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 | * */ | ||
31 | static struct ct_vm_block * | ||
32 | get_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 | |||
78 | static 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. */ | ||
126 | static struct ct_vm_block * | ||
127 | ct_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 | |||
154 | static 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 | * */ | ||
165 | static void * | ||
166 | ct_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 | |||
175 | int 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 */ | ||
223 | void 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 | } | ||