]> nv-tegra.nvidia Code Review - linux-2.6.git/blob - sound/pci/ctxfi/ctvmem.c
ALSA: ctxfi - Add prefix to debug prints
[linux-2.6.git] / sound / pci / ctxfi / ctvmem.c
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 <asm/page.h>   /* for PAGE_SIZE macro definition */
22 #include <linux/io.h>
23 #include <asm/pgtable.h>
24
25 #define CT_PTES_PER_PAGE (PAGE_SIZE / sizeof(void *))
26 #define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * PAGE_SIZE)
27
28 /* *
29  * Find or create vm block based on requested @size.
30  * @size must be page aligned.
31  * */
32 static struct ct_vm_block *
33 get_vm_block(struct ct_vm *vm, unsigned int size)
34 {
35         struct ct_vm_block *block = NULL, *entry = NULL;
36         struct list_head *pos = NULL;
37
38         list_for_each(pos, &vm->unused) {
39                 entry = list_entry(pos, struct ct_vm_block, list);
40                 if (entry->size >= size)
41                         break; /* found a block that is big enough */
42         }
43         if (pos == &vm->unused)
44                 return NULL;
45
46         if (entry->size == size) {
47                 /* Move the vm node from unused list to used list directly */
48                 list_del(&entry->list);
49                 list_add(&entry->list, &vm->used);
50                 vm->size -= size;
51                 return entry;
52         }
53
54         block = kzalloc(sizeof(*block), GFP_KERNEL);
55         if (NULL == block)
56                 return NULL;
57
58         block->addr = entry->addr;
59         block->size = size;
60         list_add(&block->list, &vm->used);
61         entry->addr += size;
62         entry->size -= size;
63         vm->size -= size;
64
65         return block;
66 }
67
68 static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block)
69 {
70         struct ct_vm_block *entry = NULL, *pre_ent = NULL;
71         struct list_head *pos = NULL, *pre = NULL;
72
73         list_del(&block->list);
74         vm->size += block->size;
75
76         list_for_each(pos, &vm->unused) {
77                 entry = list_entry(pos, struct ct_vm_block, list);
78                 if (entry->addr >= (block->addr + block->size))
79                         break; /* found a position */
80         }
81         if (pos == &vm->unused) {
82                 list_add_tail(&block->list, &vm->unused);
83                 entry = block;
84         } else {
85                 if ((block->addr + block->size) == entry->addr) {
86                         entry->addr = block->addr;
87                         entry->size += block->size;
88                         kfree(block);
89                 } else {
90                         __list_add(&block->list, pos->prev, pos);
91                         entry = block;
92                 }
93         }
94
95         pos = &entry->list;
96         pre = pos->prev;
97         while (pre != &vm->unused) {
98                 entry = list_entry(pos, struct ct_vm_block, list);
99                 pre_ent = list_entry(pre, struct ct_vm_block, list);
100                 if ((pre_ent->addr + pre_ent->size) > entry->addr)
101                         break;
102
103                 pre_ent->size += entry->size;
104                 list_del(pos);
105                 kfree(entry);
106                 pos = pre;
107                 pre = pos->prev;
108         }
109 }
110
111 /* Map host addr (kmalloced/vmalloced) to device logical addr. */
112 static struct ct_vm_block *
113 ct_vm_map(struct ct_vm *vm, void *host_addr, int size)
114 {
115         struct ct_vm_block *block = NULL;
116         unsigned long pte_start;
117         unsigned long i;
118         unsigned long pages;
119         unsigned long start_phys;
120         unsigned long *ptp;
121
122         /* do mapping */
123         if ((unsigned long)host_addr >= VMALLOC_START) {
124                 printk(KERN_ERR "ctxfi: "
125                        "Fail! Not support vmalloced addr now!\n");
126                 return NULL;
127         }
128
129         if (size > vm->size) {
130                 printk(KERN_ERR "ctxfi: Fail! No sufficient device virtural "
131                                   "memory space available!\n");
132                 return NULL;
133         }
134
135         start_phys = (virt_to_phys(host_addr) & PAGE_MASK);
136         pages = (PAGE_ALIGN(virt_to_phys(host_addr) + size)
137                         - start_phys) >> PAGE_SHIFT;
138
139         ptp = vm->ptp[0];
140
141         block = get_vm_block(vm, (pages << PAGE_SHIFT));
142         if (block == NULL) {
143                 printk(KERN_ERR "ctxfi: No virtual memory block that is big "
144                                   "enough to allocate!\n");
145                 return NULL;
146         }
147
148         pte_start = (block->addr >> PAGE_SHIFT);
149         for (i = 0; i < pages; i++)
150                 ptp[pte_start+i] = start_phys + (i << PAGE_SHIFT);
151
152         block->addr += (virt_to_phys(host_addr) & (~PAGE_MASK));
153         block->size = size;
154
155         return block;
156 }
157
158 static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block)
159 {
160         /* do unmapping */
161         block->size = ((block->addr + block->size + PAGE_SIZE - 1)
162                         & PAGE_MASK) - (block->addr & PAGE_MASK);
163         block->addr &= PAGE_MASK;
164         put_vm_block(vm, block);
165 }
166
167 /* *
168  * return the host (kmalloced) addr of the @index-th device
169  * page talbe page on success, or NULL on failure.
170  * The first returned NULL indicates the termination.
171  * */
172 static void *
173 ct_get_ptp_virt(struct ct_vm *vm, int index)
174 {
175         void *addr;
176
177         addr = (index >= CT_PTP_NUM) ? NULL : vm->ptp[index];
178
179         return addr;
180 }
181
182 int ct_vm_create(struct ct_vm **rvm)
183 {
184         struct ct_vm *vm;
185         struct ct_vm_block *block;
186         int i;
187
188         *rvm = NULL;
189
190         vm = kzalloc(sizeof(*vm), GFP_KERNEL);
191         if (NULL == vm)
192                 return -ENOMEM;
193
194         /* Allocate page table pages */
195         for (i = 0; i < CT_PTP_NUM; i++) {
196                 vm->ptp[i] = kmalloc(PAGE_SIZE, GFP_KERNEL);
197                 if (NULL == vm->ptp[i])
198                         break;
199         }
200         if (!i) {
201                 /* no page table pages are allocated */
202                 kfree(vm);
203                 return -ENOMEM;
204         }
205         vm->size = CT_ADDRS_PER_PAGE * i;
206         /* Initialise remaining ptps */
207         for (; i < CT_PTP_NUM; i++)
208                 vm->ptp[i] = NULL;
209
210         vm->map = ct_vm_map;
211         vm->unmap = ct_vm_unmap;
212         vm->get_ptp_virt = ct_get_ptp_virt;
213         INIT_LIST_HEAD(&vm->unused);
214         INIT_LIST_HEAD(&vm->used);
215         block = kzalloc(sizeof(*block), GFP_KERNEL);
216         if (NULL != block) {
217                 block->addr = 0;
218                 block->size = vm->size;
219                 list_add(&block->list, &vm->unused);
220         }
221
222         *rvm = vm;
223         return 0;
224 }
225
226 /* The caller must ensure no mapping pages are being used
227  * by hardware before calling this function */
228 void ct_vm_destroy(struct ct_vm *vm)
229 {
230         int i;
231         struct list_head *pos = NULL;
232         struct ct_vm_block *entry = NULL;
233
234         /* free used and unused list nodes */
235         while (!list_empty(&vm->used)) {
236                 pos = vm->used.next;
237                 list_del(pos);
238                 entry = list_entry(pos, struct ct_vm_block, list);
239                 kfree(entry);
240         }
241         while (!list_empty(&vm->unused)) {
242                 pos = vm->unused.next;
243                 list_del(pos);
244                 entry = list_entry(pos, struct ct_vm_block, list);
245                 kfree(entry);
246         }
247
248         /* free allocated page table pages */
249         for (i = 0; i < CT_PTP_NUM; i++)
250                 kfree(vm->ptp[i]);
251
252         vm->size = 0;
253
254         kfree(vm);
255 }