aboutsummaryrefslogtreecommitdiffstats
path: root/old/xenolinux-2.4.16-sparse/arch/xeno/drivers/dom0/dom0_memory.c
blob: 9d14070a1e6d4de6154fe95237a468f0990b7cda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/swap.h>
#include <linux/smp_lock.h>
#include <linux/swapctl.h>
#include <linux/iobuf.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>
#include <linux/list.h>

#include <asm/pgalloc.h>
#include <asm/uaccess.h>
#include <asm/tlb.h>
#include <asm/mmu.h>

#include "dom0_ops.h"

#define MAP_CONT    0
#define MAP_DISCONT 1

extern struct list_head * find_direct(struct list_head *, unsigned long);

/*
 * bd240: functions below perform direct mapping to the real physical pages
 * needed for mapping various hypervisor specific structures needed in dom0
 * userspace by various management applications such as domain builder etc.
 */

#define direct_set_pte(pteptr, pteval) queue_l1_entry_update(__pa(pteptr)|PGREQ_UNCHECKED_UPDATE, (pteval).pte_low)

#define direct_pte_clear(pteptr) queue_l1_entry_update(__pa(pteptr)|PGREQ_UNCHECKED_UPDATE, 0)

#define __direct_pte(x) ((pte_t) { (x) } )
#define __direct_mk_pte(page_nr,pgprot) __direct_pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))
#define direct_mk_pte_phys(physpage, pgprot)   __direct_mk_pte((physpage) >> PAGE_SHIFT, pgprot)

static inline void forget_pte(pte_t page)
{
    if (!pte_none(page)) {
        printk("forget_pte: old mapping existed!\n");
        BUG();
    }
}

static inline void direct_remappte_range(pte_t * pte, unsigned long address, unsigned long size,
	unsigned long phys_addr, pgprot_t prot)
{
	unsigned long end;

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	do {
		pte_t oldpage;
		oldpage = ptep_get_and_clear(pte);

 		direct_set_pte(pte, direct_mk_pte_phys(phys_addr, prot));

		forget_pte(oldpage);
		address += PAGE_SIZE;
		phys_addr += PAGE_SIZE;
		pte++;
	} while (address && (address < end));

}

static inline int direct_remappmd_range(struct mm_struct *mm, pmd_t * pmd, unsigned long address, unsigned long size,
	unsigned long phys_addr, pgprot_t prot)
{
	unsigned long end;

	address &= ~PGDIR_MASK;
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
	phys_addr -= address;
	do {
		pte_t * pte = pte_alloc(mm, pmd, address);
		if (!pte)
			return -ENOMEM;
		direct_remappte_range(pte, address, end - address, address + phys_addr, prot);
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
	} while (address && (address < end));
	return 0;
}

/*  Note: this is only safe if the mm semaphore is held when called. */
int direct_remap_page_range(unsigned long from, unsigned long phys_addr, unsigned long size, pgprot_t prot)
{
	int error = 0;
	pgd_t * dir;
	unsigned long beg = from;
	unsigned long end = from + size;
	struct mm_struct *mm = current->mm;

	phys_addr -= from;
	dir = pgd_offset(mm, from);
	flush_cache_range(mm, beg, end);
	if (from >= end)
		BUG();

	spin_lock(&mm->page_table_lock);
	do {
		pmd_t *pmd = pmd_alloc(mm, dir, from);
		error = -ENOMEM;
		if (!pmd)
			break;
		error = direct_remappmd_range(mm, pmd, from, end - from, phys_addr + from, prot);
		if (error)
			break;
		from = (from + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (from && (from < end));
	spin_unlock(&mm->page_table_lock);
	flush_tlb_range(mm, beg, end);
	return error;
}

/* 
 * used for remapping discontiguous bits of domain's memory, pages to map are
 * found from frame table beginning at the given first_pg index
 */ 
int direct_remap_disc_page_range(unsigned long from, 
                unsigned long first_pg, int tot_pages, pgprot_t prot)
{
    dom0_op_t dom0_op;
    unsigned long *pfns = get_free_page(GFP_KERNEL);
    unsigned long start = from;
    int pages, i;

    while ( tot_pages != 0 )
    {
        dom0_op.cmd = DOM0_GETMEMLIST;
        dom0_op.u.getmemlist.start_pfn = first_pg;
        pages = 1023;
        dom0_op.u.getmemlist.num_pfns = 1024;
        if ( tot_pages < 1024 )
            dom0_op.u.getmemlist.num_pfns = pages = tot_pages;
        dom0_op.u.getmemlist.buffer = pfns;
        (void)HYPERVISOR_dom0_op(&dom0_op);
        first_pg = pfns[1023]; 

        for ( i = 0; i < pages; i++ )
        {
            if(direct_remap_page_range(start, pfns[i] << PAGE_SHIFT, 
                                       PAGE_SIZE, prot))
                goto out;
            start += PAGE_SIZE;
            tot_pages--;
        }
    }

out:
    free_page(pfns);
    return tot_pages;
} 
           
/* below functions replace standard sys_mmap and sys_munmap which are absolutely useless
 * for direct memory mapping. direct_zap* functions are minor ammendments to the 
 * original versions in mm/memory.c. the changes are to enable unmapping of real physical
 * addresses.
 */

unsigned long direct_mmap(unsigned long phys_addr, unsigned long size, 
                pgprot_t prot, int flag, int tot_pages)
{
    direct_mmap_node_t * dmmap;
    struct list_head * entry;
    unsigned long addr;
    int ret = 0;
    
    if(!capable(CAP_SYS_ADMIN)){
        ret = -EPERM;
        goto out;
    }

    /* get unmapped area invokes xen specific arch_get_unmapped_area */
    addr = get_unmapped_area(NULL, 0, size, 0, 0);
    if(addr & ~PAGE_MASK){
        ret = -ENOMEM;
        goto out;
    }

    /* add node on the list of directly mapped areas, make sure the
     * list remains sorted.
     */ 
    dmmap = (direct_mmap_node_t *)kmalloc(sizeof(direct_mmap_node_t), GFP_KERNEL);
    dmmap->vm_start = addr;
    dmmap->vm_end = addr + size;
	entry = find_direct(&current->mm->context.direct_list, addr);
	if(entry != &current->mm->context.direct_list){
		list_add_tail(&dmmap->list, entry);
	} else {
    	list_add_tail(&dmmap->list, &current->mm->context.direct_list);
	}

    /* and perform the mapping */
    if(flag == MAP_DISCONT){
        ret = direct_remap_disc_page_range(addr, phys_addr >> PAGE_SHIFT, 
            tot_pages, prot);
    } else {
        ret = direct_remap_page_range(addr, phys_addr, size, prot);
    }

    if(ret == 0)
        ret = addr;

out: 
    return ret;
}

/* most of the checks, refcnt updates, cache stuff have been thrown out as they are not
 * needed
 */
static inline int direct_zap_pte_range(mmu_gather_t *tlb, pmd_t * pmd, unsigned long address, 
                unsigned long size)
{
	unsigned long offset;
	pte_t * ptep;
	int freed = 0;

	if (pmd_none(*pmd))
		return 0;
	if (pmd_bad(*pmd)) {
		pmd_ERROR(*pmd);
		pmd_clear(pmd);
		return 0;
	}
	ptep = pte_offset(pmd, address);
	offset = address & ~PMD_MASK;
	if (offset + size > PMD_SIZE)
		size = PMD_SIZE - offset;
	size &= PAGE_MASK;
	for (offset=0; offset < size; ptep++, offset += PAGE_SIZE) {
		pte_t pte = *ptep;
		if (pte_none(pte))
			continue;
		freed ++;
		direct_pte_clear(ptep);
	}

	return freed;
}

static inline int direct_zap_pmd_range(mmu_gather_t *tlb, pgd_t * dir, 
                unsigned long address, unsigned long size)
{
	pmd_t * pmd;
	unsigned long end;
	int freed;

	if (pgd_none(*dir))
		return 0;
	if (pgd_bad(*dir)) {
		pgd_ERROR(*dir);
		pgd_clear(dir);
		return 0;
	}
	pmd = pmd_offset(dir, address);
	end = address + size;
	if (end > ((address + PGDIR_SIZE) & PGDIR_MASK))
		end = ((address + PGDIR_SIZE) & PGDIR_MASK);
	freed = 0;
	do {
		freed += direct_zap_pte_range(tlb, pmd, address, end - address);
		address = (address + PMD_SIZE) & PMD_MASK; 
		pmd++;
	} while (address < end);
	return freed;
}

/*
 * remove user pages in a given range.
 */
void direct_zap_page_range(struct mm_struct *mm, unsigned long address, unsigned long size)
{
	mmu_gather_t *tlb;
	pgd_t * dir;
	unsigned long start = address, end = address + size;
	int freed = 0;

	dir = pgd_offset(mm, address);

	/*
	 * This is a long-lived spinlock. That's fine.
	 * There's no contention, because the page table
	 * lock only protects against kswapd anyway, and
	 * even if kswapd happened to be looking at this
	 * process we _want_ it to get stuck.
	 */
	if (address >= end)
		BUG();
	spin_lock(&mm->page_table_lock);
	flush_cache_range(mm, address, end);
	tlb = tlb_gather_mmu(mm);

	do {
		freed += direct_zap_pmd_range(tlb, dir, address, end - address);
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (address && (address < end));

	/* this will flush any remaining tlb entries */
	tlb_finish_mmu(tlb, start, end);

    /* decrementing rss removed */

	spin_unlock(&mm->page_table_lock);
}

int direct_unmap(unsigned long addr, unsigned long size)
{
    direct_mmap_node_t * node;
    struct list_head * curr;
    struct list_head * direct_list = &current->mm->context.direct_list;    

    curr = direct_list->next;
    while(curr != direct_list){
        node = list_entry(curr, direct_mmap_node_t, list);
        if(node->vm_start == addr)
            break;
        curr = curr->next;
    }

    if(curr == direct_list)
        return -1;

    list_del(&node->list);
    kfree(node);

    direct_zap_page_range(current->mm, addr, size);
 
    return 0;
}

int direct_disc_unmap(unsigned long from, unsigned long first_pg, int tot_pages)
{
    int count = 0;
    direct_mmap_node_t * node;
    struct list_head * curr;
    struct list_head * direct_list = &current->mm->context.direct_list;    

    curr = direct_list->next;
    while(curr != direct_list){
        node = list_entry(curr, direct_mmap_node_t, list);

        if(node->vm_start == from)
            break;
        curr = curr->next;
    }

    if(curr == direct_list)
        return -1;

    list_del(&node->list);
    kfree(node);

    while(count < tot_pages){
            direct_zap_page_range(current->mm, from, PAGE_SIZE);
            from += PAGE_SIZE;
            count++;
    }

    return 0;
}