Commit | Line | Data |
---|---|---|
8cc72361 WYC |
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> | |
8cc72361 | 21 | #include <linux/io.h> |
c76157d9 | 22 | #include <sound/pcm.h> |
8cc72361 | 23 | |
cd391e20 TI |
24 | #define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *)) |
25 | #define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE) | |
8cc72361 WYC |
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 | { | |
514eef9c TI |
34 | struct ct_vm_block *block = NULL, *entry; |
35 | struct list_head *pos; | |
8cc72361 | 36 | |
c76157d9 TI |
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 | ||
8a4259bf | 44 | mutex_lock(&vm->lock); |
8cc72361 WYC |
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) | |
8a4259bf | 51 | goto out; |
8cc72361 WYC |
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; | |
8a4259bf TI |
58 | block = entry; |
59 | goto out; | |
8cc72361 WYC |
60 | } |
61 | ||
62 | block = kzalloc(sizeof(*block), GFP_KERNEL); | |
35ebf6e7 | 63 | if (!block) |
8a4259bf | 64 | goto out; |
8cc72361 WYC |
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 | ||
8a4259bf TI |
73 | out: |
74 | mutex_unlock(&vm->lock); | |
8cc72361 WYC |
75 | return block; |
76 | } | |
77 | ||
78 | static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block) | |
79 | { | |
514eef9c TI |
80 | struct ct_vm_block *entry, *pre_ent; |
81 | struct list_head *pos, *pre; | |
8cc72361 | 82 | |
c76157d9 TI |
83 | block->size = CT_PAGE_ALIGN(block->size); |
84 | ||
8a4259bf | 85 | mutex_lock(&vm->lock); |
8cc72361 WYC |
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 | } | |
8a4259bf | 122 | mutex_unlock(&vm->lock); |
8cc72361 WYC |
123 | } |
124 | ||
125 | /* Map host addr (kmalloced/vmalloced) to device logical addr. */ | |
126 | static struct ct_vm_block * | |
c76157d9 | 127 | ct_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size) |
8cc72361 | 128 | { |
c76157d9 TI |
129 | struct ct_vm_block *block; |
130 | unsigned int pte_start; | |
131 | unsigned i, pages; | |
8cc72361 WYC |
132 | unsigned long *ptp; |
133 | ||
c76157d9 | 134 | block = get_vm_block(vm, size); |
8cc72361 | 135 | if (block == NULL) { |
b3e0afe6 | 136 | printk(KERN_ERR "ctxfi: No virtual memory block that is big " |
8cc72361 WYC |
137 | "enough to allocate!\n"); |
138 | return NULL; | |
139 | } | |
140 | ||
c76157d9 | 141 | ptp = vm->ptp[0]; |
cd391e20 | 142 | pte_start = (block->addr >> CT_PAGE_SHIFT); |
c76157d9 TI |
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 | } | |
8cc72361 | 149 | |
8cc72361 | 150 | block->size = size; |
8cc72361 WYC |
151 | return block; |
152 | } | |
153 | ||
154 | static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block) | |
155 | { | |
156 | /* do unmapping */ | |
8cc72361 WYC |
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); | |
35ebf6e7 | 184 | if (!vm) |
8cc72361 WYC |
185 | return -ENOMEM; |
186 | ||
8a4259bf TI |
187 | mutex_init(&vm->lock); |
188 | ||
8cc72361 WYC |
189 | /* Allocate page table pages */ |
190 | for (i = 0; i < CT_PTP_NUM; i++) { | |
191 | vm->ptp[i] = kmalloc(PAGE_SIZE, GFP_KERNEL); | |
35ebf6e7 | 192 | if (!vm->ptp[i]) |
8cc72361 WYC |
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; | |
514eef9c TI |
226 | struct list_head *pos; |
227 | struct ct_vm_block *entry; | |
8cc72361 WYC |
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 | } |