highmem.c 13.5 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * High memory handling common code and variables.
 *
 * (C) 1999 Andrea Arcangeli, SuSE GmbH, andrea@suse.de
 *          Gerhard Wichert, Siemens AG, Gerhard.Wichert@pdb.siemens.de
 *
 *
 * Redesigned the x86 32-bit VM architecture to deal with
 * 64-bit physical space. With current x86 CPUs this
 * means up to 64 Gigabytes physical RAM.
 *
 * Rewrote high memory support to move the page cache into
 * high memory. Implemented permanent (schedulable) kmaps
 * based on Linus' idea.
 *
 * Copyright (C) 1999 Ingo Molnar <mingo@redhat.com>
 */

#include <linux/mm.h>
20
#include <linux/module.h>
21
#include <linux/swap.h>
22
#include <linux/bio.h>
Linus Torvalds's avatar
Linus Torvalds committed
23
#include <linux/pagemap.h>
Linus Torvalds's avatar
Linus Torvalds committed
24
#include <linux/mempool.h>
Linus Torvalds's avatar
Linus Torvalds committed
25
#include <linux/blkdev.h>
26
#include <linux/init.h>
Andrew Morton's avatar
Andrew Morton committed
27
#include <linux/hash.h>
28
#include <linux/highmem.h>
29
#include <asm/tlbflush.h>
Linus Torvalds's avatar
Linus Torvalds committed
30 31 32 33 34

static mempool_t *page_pool, *isa_page_pool;

static void *page_pool_alloc(int gfp_mask, void *data)
{
Linus Torvalds's avatar
Linus Torvalds committed
35
	int gfp = gfp_mask | (int) (long) data;
Linus Torvalds's avatar
Linus Torvalds committed
36 37

	return alloc_page(gfp);
Linus Torvalds's avatar
Linus Torvalds committed
38 39 40 41 42 43
}

static void page_pool_free(void *page, void *data)
{
	__free_page(page);
}
Linus Torvalds's avatar
Linus Torvalds committed
44 45 46 47 48 49 50 51 52

/*
 * Virtual_count is not a pure "count".
 *  0 means that it is not mapped, and has not been mapped
 *    since a TLB flush - it is usable.
 *  1 means that there are no users, but it has been mapped
 *    since the last TLB flush - so we can't use it.
 *  n means that there are (n-1) current users of it.
 */
Linus Torvalds's avatar
Linus Torvalds committed
53
#ifdef CONFIG_HIGHMEM
Linus Torvalds's avatar
Linus Torvalds committed
54 55
static int pkmap_count[LAST_PKMAP];
static unsigned int last_pkmap_nr;
Linus Torvalds's avatar
Linus Torvalds committed
56
static spinlock_t kmap_lock __cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED;
Linus Torvalds's avatar
Linus Torvalds committed
57 58 59 60 61 62 63 64 65

pte_t * pkmap_page_table;

static DECLARE_WAIT_QUEUE_HEAD(pkmap_map_wait);

static void flush_all_zero_pkmaps(void)
{
	int i;

66
	flush_cache_kmaps();
Linus Torvalds's avatar
Linus Torvalds committed
67 68 69

	for (i = 0; i < LAST_PKMAP; i++) {
		struct page *page;
Linus Torvalds's avatar
Linus Torvalds committed
70

Linus Torvalds's avatar
Linus Torvalds committed
71 72 73 74 75 76 77 78 79
		/*
		 * zero means we don't have anything to do,
		 * >1 means that it is still in use. Only
		 * a count of 1 means that it is free but
		 * needs to be unmapped
		 */
		if (pkmap_count[i] != 1)
			continue;
		pkmap_count[i] = 0;
Linus Torvalds's avatar
Linus Torvalds committed
80 81 82

		/* sanity check */
		if (pte_none(pkmap_page_table[i]))
Linus Torvalds's avatar
Linus Torvalds committed
83
			BUG();
Linus Torvalds's avatar
Linus Torvalds committed
84 85 86 87 88 89 90 91 92 93 94

		/*
		 * Don't need an atomic fetch-and-clear op here;
		 * no-one has the page mapped, and cannot get at
		 * its virtual address (and hence PTE) without first
		 * getting the kmap_lock (which is held here).
		 * So no dangers, even with speculative execution.
		 */
		page = pte_page(pkmap_page_table[i]);
		pte_clear(&pkmap_page_table[i]);

Andrew Morton's avatar
Andrew Morton committed
95
		set_page_address(page, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
96
	}
97
	flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
Linus Torvalds's avatar
Linus Torvalds committed
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
}

static inline unsigned long map_new_virtual(struct page *page)
{
	unsigned long vaddr;
	int count;

start:
	count = LAST_PKMAP;
	/* Find an empty entry */
	for (;;) {
		last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
		if (!last_pkmap_nr) {
			flush_all_zero_pkmaps();
			count = LAST_PKMAP;
		}
		if (!pkmap_count[last_pkmap_nr])
			break;	/* Found a usable entry */
		if (--count)
			continue;

		/*
		 * Sleep for somebody else to unmap their entries
		 */
		{
			DECLARE_WAITQUEUE(wait, current);

125
			__set_current_state(TASK_UNINTERRUPTIBLE);
Linus Torvalds's avatar
Linus Torvalds committed
126 127 128 129 130 131 132
			add_wait_queue(&pkmap_map_wait, &wait);
			spin_unlock(&kmap_lock);
			schedule();
			remove_wait_queue(&pkmap_map_wait, &wait);
			spin_lock(&kmap_lock);

			/* Somebody else might have mapped it while we slept */
Andrew Morton's avatar
Andrew Morton committed
133 134
			if (page_address(page))
				return (unsigned long)page_address(page);
Linus Torvalds's avatar
Linus Torvalds committed
135 136 137 138 139 140 141 142 143

			/* Re-start */
			goto start;
		}
	}
	vaddr = PKMAP_ADDR(last_pkmap_nr);
	set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

	pkmap_count[last_pkmap_nr] = 1;
Andrew Morton's avatar
Andrew Morton committed
144
	set_page_address(page, (void *)vaddr);
Linus Torvalds's avatar
Linus Torvalds committed
145 146 147 148

	return vaddr;
}

149
void fastcall *kmap_high(struct page *page)
Linus Torvalds's avatar
Linus Torvalds committed
150 151 152 153 154 155 156 157 158 159
{
	unsigned long vaddr;

	/*
	 * For highmem pages, we can't trust "virtual" until
	 * after we have the lock.
	 *
	 * We cannot call this from interrupts, as it may block
	 */
	spin_lock(&kmap_lock);
Andrew Morton's avatar
Andrew Morton committed
160
	vaddr = (unsigned long)page_address(page);
Linus Torvalds's avatar
Linus Torvalds committed
161 162 163 164 165 166 167 168 169
	if (!vaddr)
		vaddr = map_new_virtual(page);
	pkmap_count[PKMAP_NR(vaddr)]++;
	if (pkmap_count[PKMAP_NR(vaddr)] < 2)
		BUG();
	spin_unlock(&kmap_lock);
	return (void*) vaddr;
}

170 171
EXPORT_SYMBOL(kmap_high);

172
void fastcall kunmap_high(struct page *page)
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175
{
	unsigned long vaddr;
	unsigned long nr;
Linus Torvalds's avatar
Linus Torvalds committed
176
	int need_wakeup;
Linus Torvalds's avatar
Linus Torvalds committed
177 178

	spin_lock(&kmap_lock);
Andrew Morton's avatar
Andrew Morton committed
179
	vaddr = (unsigned long)page_address(page);
Linus Torvalds's avatar
Linus Torvalds committed
180 181 182 183 184 185 186 187
	if (!vaddr)
		BUG();
	nr = PKMAP_NR(vaddr);

	/*
	 * A count must never go down to zero
	 * without a TLB flush!
	 */
Linus Torvalds's avatar
Linus Torvalds committed
188
	need_wakeup = 0;
Linus Torvalds's avatar
Linus Torvalds committed
189 190 191 192
	switch (--pkmap_count[nr]) {
	case 0:
		BUG();
	case 1:
Linus Torvalds's avatar
Linus Torvalds committed
193 194 195 196 197 198 199 200 201 202 203
		/*
		 * Avoid an unnecessary wake_up() function call.
		 * The common case is pkmap_count[] == 1, but
		 * no waiters.
		 * The tasks queued in the wait-queue are guarded
		 * by both the lock in the wait-queue-head and by
		 * the kmap_lock.  As the kmap_lock is held here,
		 * no need for the wait-queue-head's lock.  Simply
		 * test if the queue is empty.
		 */
		need_wakeup = waitqueue_active(&pkmap_map_wait);
Linus Torvalds's avatar
Linus Torvalds committed
204 205
	}
	spin_unlock(&kmap_lock);
Linus Torvalds's avatar
Linus Torvalds committed
206 207 208 209

	/* do wake-up, if needed, race-free outside of the spin lock */
	if (need_wakeup)
		wake_up(&pkmap_map_wait);
Linus Torvalds's avatar
Linus Torvalds committed
210 211
}

212 213
EXPORT_SYMBOL(kunmap_high);

Linus Torvalds's avatar
Linus Torvalds committed
214
#define POOL_SIZE	64
Linus Torvalds's avatar
Linus Torvalds committed
215

Linus Torvalds's avatar
Linus Torvalds committed
216 217 218 219 220 221 222 223 224 225 226 227
static __init int init_emergency_pool(void)
{
	struct sysinfo i;
	si_meminfo(&i);
	si_swapinfo(&i);
        
	if (!i.totalhigh)
		return 0;

	page_pool = mempool_create(POOL_SIZE, page_pool_alloc, page_pool_free, NULL);
	if (!page_pool)
		BUG();
Linus Torvalds's avatar
Linus Torvalds committed
228
	printk("highmem bounce pool size: %d pages\n", POOL_SIZE);
Linus Torvalds's avatar
Linus Torvalds committed
229 230 231 232

	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
233 234 235 236 237
__initcall(init_emergency_pool);

/*
 * highmem version, map in to vec
 */
238
static void bounce_copy_vec(struct bio_vec *to, unsigned char *vfrom)
Linus Torvalds's avatar
Linus Torvalds committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
{
	unsigned long flags;
	unsigned char *vto;

	local_irq_save(flags);
	vto = kmap_atomic(to->bv_page, KM_BOUNCE_READ);
	memcpy(vto + to->bv_offset, vfrom, to->bv_len);
	kunmap_atomic(vto, KM_BOUNCE_READ);
	local_irq_restore(flags);
}

#else /* CONFIG_HIGHMEM */

#define bounce_copy_vec(to, vfrom)	\
	memcpy(page_address((to)->bv_page) + (to)->bv_offset, vfrom, (to)->bv_len)

#endif

#define ISA_POOL_SIZE	16

Linus Torvalds's avatar
Linus Torvalds committed
259 260 261 262 263 264 265 266 267
/*
 * gets called "every" time someone init's a queue with BLK_BOUNCE_ISA
 * as the max address, so check if the pool has already been created.
 */
int init_emergency_isa_pool(void)
{
	if (isa_page_pool)
		return 0;

Linus Torvalds's avatar
Linus Torvalds committed
268
	isa_page_pool = mempool_create(ISA_POOL_SIZE, page_pool_alloc, page_pool_free, (void *) __GFP_DMA);
Linus Torvalds's avatar
Linus Torvalds committed
269 270 271 272 273 274 275
	if (!isa_page_pool)
		BUG();

	printk("isa bounce pool size: %d pages\n", ISA_POOL_SIZE);
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
276
/*
Linus Torvalds's avatar
Linus Torvalds committed
277 278 279
 * Simple bounce buffer support for highmem pages. Depending on the
 * queue gfp mask set, *to may or may not be a highmem page. kmap it
 * always, it will do the Right Thing
Linus Torvalds's avatar
Linus Torvalds committed
280
 */
281
static void copy_to_high_bio_irq(struct bio *to, struct bio *from)
Linus Torvalds's avatar
Linus Torvalds committed
282
{
Linus Torvalds's avatar
Linus Torvalds committed
283
	unsigned char *vfrom;
Linus Torvalds's avatar
Linus Torvalds committed
284 285
	struct bio_vec *tovec, *fromvec;
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
286

287
	__bio_for_each_segment(tovec, to, i, 0) {
Linus Torvalds's avatar
Linus Torvalds committed
288
		fromvec = from->bi_io_vec + i;
Linus Torvalds's avatar
Linus Torvalds committed
289

Linus Torvalds's avatar
Linus Torvalds committed
290 291 292 293 294
		/*
		 * not bounced
		 */
		if (tovec->bv_page == fromvec->bv_page)
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
295

296 297 298 299 300 301
		/*
		 * fromvec->bv_offset and fromvec->bv_len might have been
		 * modified by the block layer, so use the original copy,
		 * bounce_copy_vec already uses tovec->bv_len
		 */
		vfrom = page_address(fromvec->bv_page) + tovec->bv_offset;
Linus Torvalds's avatar
Linus Torvalds committed
302

Jens Axboe's avatar
Jens Axboe committed
303
		flush_dcache_page(tovec->bv_page);
Linus Torvalds's avatar
Linus Torvalds committed
304
		bounce_copy_vec(tovec, vfrom);
Linus Torvalds's avatar
Linus Torvalds committed
305
	}
Linus Torvalds's avatar
Linus Torvalds committed
306 307
}

308
static void bounce_end_io(struct bio *bio, mempool_t *pool, int err)
Linus Torvalds's avatar
Linus Torvalds committed
309
{
Linus Torvalds's avatar
Linus Torvalds committed
310
	struct bio *bio_orig = bio->bi_private;
Linus Torvalds's avatar
Linus Torvalds committed
311
	struct bio_vec *bvec, *org_vec;
312
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
313

314 315
	if (test_bit(BIO_EOPNOTSUPP, &bio->bi_flags))
		set_bit(BIO_EOPNOTSUPP, &bio_orig->bi_flags);
Linus Torvalds's avatar
Linus Torvalds committed
316

Linus Torvalds's avatar
Linus Torvalds committed
317 318 319
	/*
	 * free up bounce indirect pages used
	 */
320
	__bio_for_each_segment(bvec, bio, i, 0) {
Linus Torvalds's avatar
Linus Torvalds committed
321
		org_vec = bio_orig->bi_io_vec + i;
Linus Torvalds's avatar
Linus Torvalds committed
322 323
		if (bvec->bv_page == org_vec->bv_page)
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
324

Linus Torvalds's avatar
Linus Torvalds committed
325
		mempool_free(bvec->bv_page, pool);	
Linus Torvalds's avatar
Linus Torvalds committed
326
	}
Linus Torvalds's avatar
Linus Torvalds committed
327

328
	bio_endio(bio_orig, bio_orig->bi_size, err);
Linus Torvalds's avatar
Linus Torvalds committed
329
	bio_put(bio);
Linus Torvalds's avatar
Linus Torvalds committed
330 331
}

332
static int bounce_end_io_write(struct bio *bio, unsigned int bytes_done,int err)
Linus Torvalds's avatar
Linus Torvalds committed
333
{
334 335 336
	if (bio->bi_size)
		return 1;

337
	bounce_end_io(bio, page_pool, err);
338
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
339 340
}

341
static int bounce_end_io_write_isa(struct bio *bio, unsigned int bytes_done, int err)
Linus Torvalds's avatar
Linus Torvalds committed
342
{
343 344 345
	if (bio->bi_size)
		return 1;

346
	bounce_end_io(bio, isa_page_pool, err);
347
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
348 349
}

350
static void __bounce_end_io_read(struct bio *bio, mempool_t *pool, int err)
Linus Torvalds's avatar
Linus Torvalds committed
351 352 353 354 355
{
	struct bio *bio_orig = bio->bi_private;

	if (test_bit(BIO_UPTODATE, &bio->bi_flags))
		copy_to_high_bio_irq(bio_orig, bio);
Linus Torvalds's avatar
Linus Torvalds committed
356

357
	bounce_end_io(bio, pool, err);
Linus Torvalds's avatar
Linus Torvalds committed
358 359
}

360
static int bounce_end_io_read(struct bio *bio, unsigned int bytes_done, int err)
Linus Torvalds's avatar
Linus Torvalds committed
361
{
362 363 364
	if (bio->bi_size)
		return 1;

365
	__bounce_end_io_read(bio, page_pool, err);
366
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
367 368
}

369
static int bounce_end_io_read_isa(struct bio *bio, unsigned int bytes_done, int err)
Linus Torvalds's avatar
Linus Torvalds committed
370
{
371 372 373
	if (bio->bi_size)
		return 1;

374
	__bounce_end_io_read(bio, isa_page_pool, err);
375
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
376 377
}

Andrew Morton's avatar
Andrew Morton committed
378
static void __blk_queue_bounce(request_queue_t *q, struct bio **bio_orig,
Jens Axboe's avatar
Jens Axboe committed
379
			mempool_t *pool)
Linus Torvalds's avatar
Linus Torvalds committed
380
{
Linus Torvalds's avatar
Linus Torvalds committed
381
	struct page *page;
Linus Torvalds's avatar
Linus Torvalds committed
382
	struct bio *bio = NULL;
Jens Axboe's avatar
Jens Axboe committed
383
	int i, rw = bio_data_dir(*bio_orig);
Linus Torvalds's avatar
Linus Torvalds committed
384
	struct bio_vec *to, *from;
Linus Torvalds's avatar
Linus Torvalds committed
385

Linus Torvalds's avatar
Linus Torvalds committed
386 387 388 389 390 391
	bio_for_each_segment(from, *bio_orig, i) {
		page = from->bv_page;

		/*
		 * is destination page below bounce pfn?
		 */
392
		if (page_to_pfn(page) < q->bounce_pfn)
Linus Torvalds's avatar
Linus Torvalds committed
393 394 395 396 397 398
			continue;

		/*
		 * irk, bounce it
		 */
		if (!bio)
Andrew Morton's avatar
Andrew Morton committed
399
			bio = bio_alloc(GFP_NOIO, (*bio_orig)->bi_vcnt);
Linus Torvalds's avatar
Linus Torvalds committed
400

Linus Torvalds's avatar
Linus Torvalds committed
401
		to = bio->bi_io_vec + i;
Linus Torvalds's avatar
Linus Torvalds committed
402

Jens Axboe's avatar
Jens Axboe committed
403
		to->bv_page = mempool_alloc(pool, q->bounce_gfp);
Linus Torvalds's avatar
Linus Torvalds committed
404 405 406
		to->bv_len = from->bv_len;
		to->bv_offset = from->bv_offset;

Jens Axboe's avatar
Jens Axboe committed
407
		if (rw == WRITE) {
Linus Torvalds's avatar
Linus Torvalds committed
408 409
			char *vto, *vfrom;

Jens Axboe's avatar
Jens Axboe committed
410
			flush_dcache_page(from->bv_page);
Linus Torvalds's avatar
Linus Torvalds committed
411
			vto = page_address(to->bv_page) + to->bv_offset;
Linus Torvalds's avatar
Linus Torvalds committed
412 413 414
			vfrom = kmap(from->bv_page) + from->bv_offset;
			memcpy(vto, vfrom, to->bv_len);
			kunmap(from->bv_page);
Linus Torvalds's avatar
Linus Torvalds committed
415 416 417 418 419 420 421 422 423 424 425 426 427 428
		}
	}

	/*
	 * no pages bounced
	 */
	if (!bio)
		return;

	/*
	 * at least one page was bounced, fill in possible non-highmem
	 * pages
	 */
	bio_for_each_segment(from, *bio_orig, i) {
Jens Axboe's avatar
Jens Axboe committed
429
		to = bio_iovec_idx(bio, i);
Linus Torvalds's avatar
Linus Torvalds committed
430 431 432
		if (!to->bv_page) {
			to->bv_page = from->bv_page;
			to->bv_len = from->bv_len;
Jens Axboe's avatar
Jens Axboe committed
433
			to->bv_offset = from->bv_offset;
Linus Torvalds's avatar
Linus Torvalds committed
434 435
		}
	}
Linus Torvalds's avatar
Linus Torvalds committed
436

437
	bio->bi_bdev = (*bio_orig)->bi_bdev;
Jens Axboe's avatar
Jens Axboe committed
438
	bio->bi_flags |= (1 << BIO_BOUNCED);
Linus Torvalds's avatar
Linus Torvalds committed
439 440
	bio->bi_sector = (*bio_orig)->bi_sector;
	bio->bi_rw = (*bio_orig)->bi_rw;
Linus Torvalds's avatar
Linus Torvalds committed
441

Linus Torvalds's avatar
Linus Torvalds committed
442
	bio->bi_vcnt = (*bio_orig)->bi_vcnt;
443
	bio->bi_idx = (*bio_orig)->bi_idx;
Linus Torvalds's avatar
Linus Torvalds committed
444
	bio->bi_size = (*bio_orig)->bi_size;
Linus Torvalds's avatar
Linus Torvalds committed
445

Linus Torvalds's avatar
Linus Torvalds committed
446
	if (pool == page_pool) {
Jens Axboe's avatar
Jens Axboe committed
447 448
		bio->bi_end_io = bounce_end_io_write;
		if (rw == READ)
Linus Torvalds's avatar
Linus Torvalds committed
449 450
			bio->bi_end_io = bounce_end_io_read;
	} else {
Jens Axboe's avatar
Jens Axboe committed
451 452
		bio->bi_end_io = bounce_end_io_write_isa;
		if (rw == READ)
Linus Torvalds's avatar
Linus Torvalds committed
453 454
			bio->bi_end_io = bounce_end_io_read_isa;
	}
Linus Torvalds's avatar
Linus Torvalds committed
455

Linus Torvalds's avatar
Linus Torvalds committed
456
	bio->bi_private = *bio_orig;
Linus Torvalds's avatar
Linus Torvalds committed
457 458
	*bio_orig = bio;
}
459

Andrew Morton's avatar
Andrew Morton committed
460
void blk_queue_bounce(request_queue_t *q, struct bio **bio_orig)
Jens Axboe's avatar
Jens Axboe committed
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
{
	mempool_t *pool;

	/*
	 * for non-isa bounce case, just check if the bounce pfn is equal
	 * to or bigger than the highest pfn in the system -- in that case,
	 * don't waste time iterating over bio segments
	 */
	if (!(q->bounce_gfp & GFP_DMA)) {
		if (q->bounce_pfn >= blk_max_pfn)
			return;
		pool = page_pool;
	} else {
		BUG_ON(!isa_page_pool);
		pool = isa_page_pool;
	}

	/*
	 * slow path
	 */
Andrew Morton's avatar
Andrew Morton committed
481
	__blk_queue_bounce(q, bio_orig, pool);
Jens Axboe's avatar
Jens Axboe committed
482 483
}

484 485
EXPORT_SYMBOL(blk_queue_bounce);

Andrew Morton's avatar
Andrew Morton committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
#if defined(HASHED_PAGE_VIRTUAL)

#define PA_HASH_ORDER	7

/*
 * Describes one page->virtual association
 */
struct page_address_map {
	struct page *page;
	void *virtual;
	struct list_head list;
};

/*
 * page_address_map freelist, allocated from page_address_maps.
 */
static struct list_head page_address_pool;	/* freelist */
static spinlock_t pool_lock;			/* protects page_address_pool */

/*
 * Hash table bucket
 */
static struct page_address_slot {
	struct list_head lh;			/* List of page_address_maps */
	spinlock_t lock;			/* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];

static struct page_address_slot *page_slot(struct page *page)
{
	return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

void *page_address(struct page *page)
{
	unsigned long flags;
	void *ret;
	struct page_address_slot *pas;

	if (!PageHighMem(page))
		return lowmem_page_address(page);

	pas = page_slot(page);
	ret = NULL;
	spin_lock_irqsave(&pas->lock, flags);
	if (!list_empty(&pas->lh)) {
		struct page_address_map *pam;

		list_for_each_entry(pam, &pas->lh, list) {
			if (pam->page == page) {
				ret = pam->virtual;
				goto done;
			}
		}
	}
done:
	spin_unlock_irqrestore(&pas->lock, flags);
	return ret;
}

545 546
EXPORT_SYMBOL(page_address);

Andrew Morton's avatar
Andrew Morton committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
void set_page_address(struct page *page, void *virtual)
{
	unsigned long flags;
	struct page_address_slot *pas;
	struct page_address_map *pam;

	BUG_ON(!PageHighMem(page));

	pas = page_slot(page);
	if (virtual) {		/* Add */
		BUG_ON(list_empty(&page_address_pool));

		spin_lock_irqsave(&pool_lock, flags);
		pam = list_entry(page_address_pool.next,
				struct page_address_map, list);
		list_del(&pam->list);
		spin_unlock_irqrestore(&pool_lock, flags);

		pam->page = page;
		pam->virtual = virtual;

		spin_lock_irqsave(&pas->lock, flags);
		list_add_tail(&pam->list, &pas->lh);
		spin_unlock_irqrestore(&pas->lock, flags);
	} else {		/* Remove */
		spin_lock_irqsave(&pas->lock, flags);
		list_for_each_entry(pam, &pas->lh, list) {
			if (pam->page == page) {
				list_del(&pam->list);
				spin_unlock_irqrestore(&pas->lock, flags);
				spin_lock_irqsave(&pool_lock, flags);
				list_add_tail(&pam->list, &page_address_pool);
				spin_unlock_irqrestore(&pool_lock, flags);
				goto done;
			}
		}
		spin_unlock_irqrestore(&pas->lock, flags);
	}
done:
	return;
}

static struct page_address_map page_address_maps[LAST_PKMAP];

void __init page_address_init(void)
{
	int i;

	INIT_LIST_HEAD(&page_address_pool);
	for (i = 0; i < ARRAY_SIZE(page_address_maps); i++)
		list_add(&page_address_maps[i].list, &page_address_pool);
	for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
		INIT_LIST_HEAD(&page_address_htable[i].lh);
		spin_lock_init(&page_address_htable[i].lock);
	}
	spin_lock_init(&pool_lock);
}

#endif	/* defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL) */