hash_native_64.c 10.1 KB
Newer Older
L
Linus Torvalds 已提交
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
/*
 * native hashtable management.
 *
 * SMP scalability work:
 *    Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/threads.h>
#include <linux/smp.h>

#include <asm/abs_addr.h>
#include <asm/machdep.h>
#include <asm/mmu.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>
#include <asm/tlb.h>
#include <asm/cputable.h>

#define HPTE_LOCK_BIT 3

static DEFINE_SPINLOCK(native_tlbie_lock);

30
static inline void native_lock_hpte(hpte_t *hptep)
L
Linus Torvalds 已提交
31
{
32
	unsigned long *word = &hptep->v;
L
Linus Torvalds 已提交
33 34 35 36 37 38 39 40 41

	while (1) {
		if (!test_and_set_bit(HPTE_LOCK_BIT, word))
			break;
		while(test_bit(HPTE_LOCK_BIT, word))
			cpu_relax();
	}
}

42
static inline void native_unlock_hpte(hpte_t *hptep)
L
Linus Torvalds 已提交
43
{
44
	unsigned long *word = &hptep->v;
L
Linus Torvalds 已提交
45 46 47 48 49 50

	asm volatile("lwsync":::"memory");
	clear_bit(HPTE_LOCK_BIT, word);
}

long native_hpte_insert(unsigned long hpte_group, unsigned long va,
51 52
			unsigned long prpn, unsigned long vflags,
			unsigned long rflags)
L
Linus Torvalds 已提交
53
{
54 55
	hpte_t *hptep = htab_address + hpte_group;
	unsigned long hpte_v, hpte_r;
L
Linus Torvalds 已提交
56 57 58
	int i;

	for (i = 0; i < HPTES_PER_GROUP; i++) {
59
		if (! (hptep->v & HPTE_V_VALID)) {
L
Linus Torvalds 已提交
60 61
			/* retry with lock held */
			native_lock_hpte(hptep);
62
			if (! (hptep->v & HPTE_V_VALID))
L
Linus Torvalds 已提交
63 64 65 66 67 68 69 70 71 72
				break;
			native_unlock_hpte(hptep);
		}

		hptep++;
	}

	if (i == HPTES_PER_GROUP)
		return -1;

73 74 75
	hpte_v = (va >> 23) << HPTE_V_AVPN_SHIFT | vflags | HPTE_V_VALID;
	if (vflags & HPTE_V_LARGE)
		va &= ~(1UL << HPTE_V_AVPN_SHIFT);
76
	hpte_r = (prpn << HPTE_R_RPN_SHIFT) | rflags;
L
Linus Torvalds 已提交
77

78
	hptep->r = hpte_r;
L
Linus Torvalds 已提交
79 80 81 82 83 84
	/* Guarantee the second dword is visible before the valid bit */
	__asm__ __volatile__ ("eieio" : : : "memory");
	/*
	 * Now set the first dword including the valid bit
	 * NOTE: this also unlocks the hpte
	 */
85
	hptep->v = hpte_v;
L
Linus Torvalds 已提交
86 87 88

	__asm__ __volatile__ ("ptesync" : : : "memory");

89
	return i | (!!(vflags & HPTE_V_SECONDARY) << 3);
L
Linus Torvalds 已提交
90 91 92 93
}

static long native_hpte_remove(unsigned long hpte_group)
{
94
	hpte_t *hptep;
L
Linus Torvalds 已提交
95 96
	int i;
	int slot_offset;
97
	unsigned long hpte_v;
L
Linus Torvalds 已提交
98 99 100 101 102 103

	/* pick a random entry to start at */
	slot_offset = mftb() & 0x7;

	for (i = 0; i < HPTES_PER_GROUP; i++) {
		hptep = htab_address + hpte_group + slot_offset;
104
		hpte_v = hptep->v;
L
Linus Torvalds 已提交
105

106
		if ((hpte_v & HPTE_V_VALID) && !(hpte_v & HPTE_V_BOLTED)) {
L
Linus Torvalds 已提交
107 108
			/* retry with lock held */
			native_lock_hpte(hptep);
109 110 111
			hpte_v = hptep->v;
			if ((hpte_v & HPTE_V_VALID)
			    && !(hpte_v & HPTE_V_BOLTED))
L
Linus Torvalds 已提交
112 113 114 115 116 117 118 119 120 121 122 123
				break;
			native_unlock_hpte(hptep);
		}

		slot_offset++;
		slot_offset &= 0x7;
	}

	if (i == HPTES_PER_GROUP)
		return -1;

	/* Invalidate the hpte. NOTE: this also unlocks it */
124
	hptep->v = 0;
L
Linus Torvalds 已提交
125 126 127 128

	return i;
}

129
static inline void set_pp_bit(unsigned long pp, hpte_t *addr)
L
Linus Torvalds 已提交
130 131
{
	unsigned long old;
132
	unsigned long *p = &addr->r;
L
Linus Torvalds 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

	__asm__ __volatile__(
	"1:	ldarx	%0,0,%3\n\
		rldimi	%0,%2,0,61\n\
		stdcx.	%0,0,%3\n\
		bne	1b"
	: "=&r" (old), "=m" (*p)
	: "r" (pp), "r" (p), "m" (*p)
	: "cc");
}

/*
 * Only works on small pages. Yes its ugly to have to check each slot in
 * the group but we only use this during bootup.
 */
static long native_hpte_find(unsigned long vpn)
{
150
	hpte_t *hptep;
L
Linus Torvalds 已提交
151 152 153
	unsigned long hash;
	unsigned long i, j;
	long slot;
154
	unsigned long hpte_v;
L
Linus Torvalds 已提交
155 156 157 158 159 160 161

	hash = hpt_hash(vpn, 0);

	for (j = 0; j < 2; j++) {
		slot = (hash & htab_hash_mask) * HPTES_PER_GROUP;
		for (i = 0; i < HPTES_PER_GROUP; i++) {
			hptep = htab_address + slot;
162
			hpte_v = hptep->v;
L
Linus Torvalds 已提交
163

164 165 166
			if ((HPTE_V_AVPN_VAL(hpte_v) == (vpn >> 11))
			    && (hpte_v & HPTE_V_VALID)
			    && ( !!(hpte_v & HPTE_V_SECONDARY) == j)) {
L
Linus Torvalds 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
				/* HPTE matches */
				if (j)
					slot = -slot;
				return slot;
			}
			++slot;
		}
		hash = ~hash;
	}

	return -1;
}

static long native_hpte_updatepp(unsigned long slot, unsigned long newpp,
				 unsigned long va, int large, int local)
{
183 184
	hpte_t *hptep = htab_address + slot;
	unsigned long hpte_v;
L
Linus Torvalds 已提交
185 186 187 188
	unsigned long avpn = va >> 23;
	int ret = 0;

	if (large)
189
		avpn &= ~1;
L
Linus Torvalds 已提交
190 191 192

	native_lock_hpte(hptep);

193
	hpte_v = hptep->v;
L
Linus Torvalds 已提交
194 195

	/* Even if we miss, we need to invalidate the TLB */
196 197
	if ((HPTE_V_AVPN_VAL(hpte_v) != avpn)
	    || !(hpte_v & HPTE_V_VALID)) {
L
Linus Torvalds 已提交
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
		native_unlock_hpte(hptep);
		ret = -1;
	} else {
		set_pp_bit(newpp, hptep);
		native_unlock_hpte(hptep);
	}

	/* Ensure it is out of the tlb too */
	if (cpu_has_feature(CPU_FTR_TLBIEL) && !large && local) {
		tlbiel(va);
	} else {
		int lock_tlbie = !cpu_has_feature(CPU_FTR_LOCKLESS_TLBIE);

		if (lock_tlbie)
			spin_lock(&native_tlbie_lock);
		tlbie(va, large);
		if (lock_tlbie)
			spin_unlock(&native_tlbie_lock);
	}

	return ret;
}

/*
 * Update the page protection bits. Intended to be used to create
 * guard pages for kernel data structures on pages which are bolted
 * in the HPT. Assumes pages being operated on will not be stolen.
 * Does not work on large pages.
 *
 * No need to lock here because we should be the only user.
 */
static void native_hpte_updateboltedpp(unsigned long newpp, unsigned long ea)
{
	unsigned long vsid, va, vpn, flags = 0;
	long slot;
233
	hpte_t *hptep;
L
Linus Torvalds 已提交
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
	int lock_tlbie = !cpu_has_feature(CPU_FTR_LOCKLESS_TLBIE);

	vsid = get_kernel_vsid(ea);
	va = (vsid << 28) | (ea & 0x0fffffff);
	vpn = va >> PAGE_SHIFT;

	slot = native_hpte_find(vpn);
	if (slot == -1)
		panic("could not find page to bolt\n");
	hptep = htab_address + slot;

	set_pp_bit(newpp, hptep);

	/* Ensure it is out of the tlb too */
	if (lock_tlbie)
		spin_lock_irqsave(&native_tlbie_lock, flags);
	tlbie(va, 0);
	if (lock_tlbie)
		spin_unlock_irqrestore(&native_tlbie_lock, flags);
}

static void native_hpte_invalidate(unsigned long slot, unsigned long va,
				    int large, int local)
{
258 259
	hpte_t *hptep = htab_address + slot;
	unsigned long hpte_v;
L
Linus Torvalds 已提交
260 261 262 263 264
	unsigned long avpn = va >> 23;
	unsigned long flags;
	int lock_tlbie = !cpu_has_feature(CPU_FTR_LOCKLESS_TLBIE);

	if (large)
265
		avpn &= ~1;
L
Linus Torvalds 已提交
266 267 268 269

	local_irq_save(flags);
	native_lock_hpte(hptep);

270
	hpte_v = hptep->v;
L
Linus Torvalds 已提交
271 272

	/* Even if we miss, we need to invalidate the TLB */
273 274
	if ((HPTE_V_AVPN_VAL(hpte_v) != avpn)
	    || !(hpte_v & HPTE_V_VALID)) {
L
Linus Torvalds 已提交
275 276 277
		native_unlock_hpte(hptep);
	} else {
		/* Invalidate the hpte. NOTE: this also unlocks it */
278
		hptep->v = 0;
L
Linus Torvalds 已提交
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
	}

	/* Invalidate the tlb */
	if (cpu_has_feature(CPU_FTR_TLBIEL) && !large && local) {
		tlbiel(va);
	} else {
		if (lock_tlbie)
			spin_lock(&native_tlbie_lock);
		tlbie(va, large);
		if (lock_tlbie)
			spin_unlock(&native_tlbie_lock);
	}
	local_irq_restore(flags);
}

294 295 296 297 298 299 300 301 302 303 304
/*
 * clear all mappings on kexec.  All cpus are in real mode (or they will
 * be when they isi), and we are the only one left.  We rely on our kernel
 * mapping being 0xC0's and the hardware ignoring those two real bits.
 *
 * TODO: add batching support when enabled.  remember, no dynamic memory here,
 * athough there is the control page available...
 */
static void native_hpte_clear(void)
{
	unsigned long slot, slots, flags;
305 306
	hpte_t *hptep = htab_address;
	unsigned long hpte_v;
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
	unsigned long pteg_count;

	pteg_count = htab_hash_mask + 1;

	local_irq_save(flags);

	/* we take the tlbie lock and hold it.  Some hardware will
	 * deadlock if we try to tlbie from two processors at once.
	 */
	spin_lock(&native_tlbie_lock);

	slots = pteg_count * HPTES_PER_GROUP;

	for (slot = 0; slot < slots; slot++, hptep++) {
		/*
		 * we could lock the pte here, but we are the only cpu
		 * running,  right?  and for crash dump, we probably
		 * don't want to wait for a maybe bad cpu.
		 */
326
		hpte_v = hptep->v;
327

328 329 330
		if (hpte_v & HPTE_V_VALID) {
			hptep->v = 0;
			tlbie(slot2va(hpte_v, slot), hpte_v & HPTE_V_LARGE);
331 332 333 334 335 336 337
		}
	}

	spin_unlock(&native_tlbie_lock);
	local_irq_restore(flags);
}

338
static void native_flush_hash_range(unsigned long number, int local)
L
Linus Torvalds 已提交
339
{
340
	unsigned long va, vpn, hash, secondary, slot, flags, avpn;
L
Linus Torvalds 已提交
341
	int i, j;
342 343
	hpte_t *hptep;
	unsigned long hpte_v;
L
Linus Torvalds 已提交
344
	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
345
	unsigned long large = batch->large;
L
Linus Torvalds 已提交
346 347 348 349 350

	local_irq_save(flags);

	j = 0;
	for (i = 0; i < number; i++) {
351
		va = batch->vaddr[j];
L
Linus Torvalds 已提交
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
		if (large)
			vpn = va >> HPAGE_SHIFT;
		else
			vpn = va >> PAGE_SHIFT;
		hash = hpt_hash(vpn, large);
		secondary = (pte_val(batch->pte[i]) & _PAGE_SECONDARY) >> 15;
		if (secondary)
			hash = ~hash;
		slot = (hash & htab_hash_mask) * HPTES_PER_GROUP;
		slot += (pte_val(batch->pte[i]) & _PAGE_GROUP_IX) >> 12;

		hptep = htab_address + slot;

		avpn = va >> 23;
		if (large)
			avpn &= ~0x1UL;

		native_lock_hpte(hptep);

371
		hpte_v = hptep->v;
L
Linus Torvalds 已提交
372 373

		/* Even if we miss, we need to invalidate the TLB */
374 375
		if ((HPTE_V_AVPN_VAL(hpte_v) != avpn)
		    || !(hpte_v & HPTE_V_VALID)) {
L
Linus Torvalds 已提交
376 377 378
			native_unlock_hpte(hptep);
		} else {
			/* Invalidate the hpte. NOTE: this also unlocks it */
379
			hptep->v = 0;
L
Linus Torvalds 已提交
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
		}

		j++;
	}

	if (cpu_has_feature(CPU_FTR_TLBIEL) && !large && local) {
		asm volatile("ptesync":::"memory");

		for (i = 0; i < j; i++)
			__tlbiel(batch->vaddr[i]);

		asm volatile("ptesync":::"memory");
	} else {
		int lock_tlbie = !cpu_has_feature(CPU_FTR_LOCKLESS_TLBIE);

		if (lock_tlbie)
			spin_lock(&native_tlbie_lock);

		asm volatile("ptesync":::"memory");

		for (i = 0; i < j; i++)
401
			__tlbie(batch->vaddr[i], large);
L
Linus Torvalds 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440

		asm volatile("eieio; tlbsync; ptesync":::"memory");

		if (lock_tlbie)
			spin_unlock(&native_tlbie_lock);
	}

	local_irq_restore(flags);
}

#ifdef CONFIG_PPC_PSERIES
/* Disable TLB batching on nighthawk */
static inline int tlb_batching_enabled(void)
{
	struct device_node *root = of_find_node_by_path("/");
	int enabled = 1;

	if (root) {
		const char *model = get_property(root, "model", NULL);
		if (model && !strcmp(model, "IBM,9076-N81"))
			enabled = 0;
		of_node_put(root);
	}

	return enabled;
}
#else
static inline int tlb_batching_enabled(void)
{
	return 1;
}
#endif

void hpte_init_native(void)
{
	ppc_md.hpte_invalidate	= native_hpte_invalidate;
	ppc_md.hpte_updatepp	= native_hpte_updatepp;
	ppc_md.hpte_updateboltedpp = native_hpte_updateboltedpp;
	ppc_md.hpte_insert	= native_hpte_insert;
441 442
	ppc_md.hpte_remove	= native_hpte_remove;
	ppc_md.hpte_clear_all	= native_hpte_clear;
L
Linus Torvalds 已提交
443 444 445 446
	if (tlb_batching_enabled())
		ppc_md.flush_hash_range = native_flush_hash_range;
	htab_finish_init();
}