| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright 2010 Tilera Corporation. All Rights Reserved. | 
 | 3 |  * | 
 | 4 |  *   This program is free software; you can redistribute it and/or | 
 | 5 |  *   modify it under the terms of the GNU General Public License | 
 | 6 |  *   as published by the Free Software Foundation, version 2. | 
 | 7 |  * | 
 | 8 |  *   This program is distributed in the hope that it will be useful, but | 
 | 9 |  *   WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 10 |  *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | 
 | 11 |  *   NON INFRINGEMENT.  See the GNU General Public License for | 
 | 12 |  *   more details. | 
 | 13 |  */ | 
 | 14 |  | 
 | 15 | #include <linux/fs.h> | 
 | 16 | #include <linux/proc_fs.h> | 
 | 17 | #include <linux/seq_file.h> | 
 | 18 | #include <linux/rwsem.h> | 
 | 19 | #include <linux/kprobes.h> | 
 | 20 | #include <linux/sched.h> | 
 | 21 | #include <linux/hardirq.h> | 
 | 22 | #include <linux/uaccess.h> | 
 | 23 | #include <linux/smp.h> | 
 | 24 | #include <linux/cdev.h> | 
 | 25 | #include <linux/compat.h> | 
 | 26 | #include <asm/hardwall.h> | 
 | 27 | #include <asm/traps.h> | 
 | 28 | #include <asm/siginfo.h> | 
 | 29 | #include <asm/irq_regs.h> | 
 | 30 |  | 
 | 31 | #include <arch/interrupts.h> | 
 | 32 | #include <arch/spr_def.h> | 
 | 33 |  | 
 | 34 |  | 
 | 35 | /* | 
 | 36 |  * This data structure tracks the rectangle data, etc., associated | 
 | 37 |  * one-to-one with a "struct file *" from opening HARDWALL_FILE. | 
 | 38 |  * Note that the file's private data points back to this structure. | 
 | 39 |  */ | 
 | 40 | struct hardwall_info { | 
 | 41 | 	struct list_head list;             /* "rectangles" list */ | 
 | 42 | 	struct list_head task_head;        /* head of tasks in this hardwall */ | 
 | 43 | 	int ulhc_x;                        /* upper left hand corner x coord */ | 
 | 44 | 	int ulhc_y;                        /* upper left hand corner y coord */ | 
 | 45 | 	int width;                         /* rectangle width */ | 
 | 46 | 	int height;                        /* rectangle height */ | 
 | 47 | 	int teardown_in_progress;          /* are we tearing this one down? */ | 
 | 48 | }; | 
 | 49 |  | 
 | 50 | /* Currently allocated hardwall rectangles */ | 
 | 51 | static LIST_HEAD(rectangles); | 
 | 52 |  | 
 | 53 | /* | 
 | 54 |  * Guard changes to the hardwall data structures. | 
 | 55 |  * This could be finer grained (e.g. one lock for the list of hardwall | 
 | 56 |  * rectangles, then separate embedded locks for each one's list of tasks), | 
 | 57 |  * but there are subtle correctness issues when trying to start with | 
 | 58 |  * a task's "hardwall" pointer and lock the correct rectangle's embedded | 
 | 59 |  * lock in the presence of a simultaneous deactivation, so it seems | 
 | 60 |  * easier to have a single lock, given that none of these data | 
 | 61 |  * structures are touched very frequently during normal operation. | 
 | 62 |  */ | 
 | 63 | static DEFINE_SPINLOCK(hardwall_lock); | 
 | 64 |  | 
 | 65 | /* Allow disabling UDN access. */ | 
 | 66 | static int udn_disabled; | 
 | 67 | static int __init noudn(char *str) | 
 | 68 | { | 
 | 69 | 	pr_info("User-space UDN access is disabled\n"); | 
 | 70 | 	udn_disabled = 1; | 
 | 71 | 	return 0; | 
 | 72 | } | 
 | 73 | early_param("noudn", noudn); | 
 | 74 |  | 
 | 75 |  | 
 | 76 | /* | 
 | 77 |  * Low-level primitives | 
 | 78 |  */ | 
 | 79 |  | 
 | 80 | /* Set a CPU bit if the CPU is online. */ | 
 | 81 | #define cpu_online_set(cpu, dst) do { \ | 
 | 82 | 	if (cpu_online(cpu))          \ | 
 | 83 | 		cpumask_set_cpu(cpu, dst);    \ | 
 | 84 | } while (0) | 
 | 85 |  | 
 | 86 |  | 
 | 87 | /* Does the given rectangle contain the given x,y coordinate? */ | 
 | 88 | static int contains(struct hardwall_info *r, int x, int y) | 
 | 89 | { | 
 | 90 | 	return (x >= r->ulhc_x && x < r->ulhc_x + r->width) && | 
 | 91 | 		(y >= r->ulhc_y && y < r->ulhc_y + r->height); | 
 | 92 | } | 
 | 93 |  | 
 | 94 | /* Compute the rectangle parameters and validate the cpumask. */ | 
 | 95 | static int setup_rectangle(struct hardwall_info *r, struct cpumask *mask) | 
 | 96 | { | 
 | 97 | 	int x, y, cpu, ulhc, lrhc; | 
 | 98 |  | 
 | 99 | 	/* The first cpu is the ULHC, the last the LRHC. */ | 
 | 100 | 	ulhc = find_first_bit(cpumask_bits(mask), nr_cpumask_bits); | 
 | 101 | 	lrhc = find_last_bit(cpumask_bits(mask), nr_cpumask_bits); | 
 | 102 |  | 
 | 103 | 	/* Compute the rectangle attributes from the cpus. */ | 
 | 104 | 	r->ulhc_x = cpu_x(ulhc); | 
 | 105 | 	r->ulhc_y = cpu_y(ulhc); | 
 | 106 | 	r->width = cpu_x(lrhc) - r->ulhc_x + 1; | 
 | 107 | 	r->height = cpu_y(lrhc) - r->ulhc_y + 1; | 
 | 108 |  | 
 | 109 | 	/* Width and height must be positive */ | 
 | 110 | 	if (r->width <= 0 || r->height <= 0) | 
 | 111 | 		return -EINVAL; | 
 | 112 |  | 
 | 113 | 	/* Confirm that the cpumask is exactly the rectangle. */ | 
 | 114 | 	for (y = 0, cpu = 0; y < smp_height; ++y) | 
 | 115 | 		for (x = 0; x < smp_width; ++x, ++cpu) | 
 | 116 | 			if (cpumask_test_cpu(cpu, mask) != contains(r, x, y)) | 
 | 117 | 				return -EINVAL; | 
 | 118 |  | 
 | 119 | 	/* | 
 | 120 | 	 * Note that offline cpus can't be drained when this UDN | 
 | 121 | 	 * rectangle eventually closes.  We used to detect this | 
 | 122 | 	 * situation and print a warning, but it annoyed users and | 
 | 123 | 	 * they ignored it anyway, so now we just return without a | 
 | 124 | 	 * warning. | 
 | 125 | 	 */ | 
 | 126 | 	return 0; | 
 | 127 | } | 
 | 128 |  | 
 | 129 | /* Do the two given rectangles overlap on any cpu? */ | 
 | 130 | static int overlaps(struct hardwall_info *a, struct hardwall_info *b) | 
 | 131 | { | 
 | 132 | 	return a->ulhc_x + a->width > b->ulhc_x &&    /* A not to the left */ | 
 | 133 | 		b->ulhc_x + b->width > a->ulhc_x &&   /* B not to the left */ | 
 | 134 | 		a->ulhc_y + a->height > b->ulhc_y &&  /* A not above */ | 
 | 135 | 		b->ulhc_y + b->height > a->ulhc_y;    /* B not above */ | 
 | 136 | } | 
 | 137 |  | 
 | 138 |  | 
 | 139 | /* | 
 | 140 |  * Hardware management of hardwall setup, teardown, trapping, | 
 | 141 |  * and enabling/disabling PL0 access to the networks. | 
 | 142 |  */ | 
 | 143 |  | 
 | 144 | /* Bit field values to mask together for writes to SPR_XDN_DIRECTION_PROTECT */ | 
 | 145 | enum direction_protect { | 
 | 146 | 	N_PROTECT = (1 << 0), | 
 | 147 | 	E_PROTECT = (1 << 1), | 
 | 148 | 	S_PROTECT = (1 << 2), | 
 | 149 | 	W_PROTECT = (1 << 3) | 
 | 150 | }; | 
 | 151 |  | 
 | 152 | static void enable_firewall_interrupts(void) | 
 | 153 | { | 
| Chris Metcalf | 5d96611 | 2010-11-01 15:24:29 -0400 | [diff] [blame] | 154 | 	arch_local_irq_unmask_now(INT_UDN_FIREWALL); | 
| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 155 | } | 
 | 156 |  | 
 | 157 | static void disable_firewall_interrupts(void) | 
 | 158 | { | 
| Chris Metcalf | 5d96611 | 2010-11-01 15:24:29 -0400 | [diff] [blame] | 159 | 	arch_local_irq_mask_now(INT_UDN_FIREWALL); | 
| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 160 | } | 
 | 161 |  | 
 | 162 | /* Set up hardwall on this cpu based on the passed hardwall_info. */ | 
 | 163 | static void hardwall_setup_ipi_func(void *info) | 
 | 164 | { | 
 | 165 | 	struct hardwall_info *r = info; | 
 | 166 | 	int cpu = smp_processor_id(); | 
 | 167 | 	int x = cpu % smp_width; | 
 | 168 | 	int y = cpu / smp_width; | 
 | 169 | 	int bits = 0; | 
 | 170 | 	if (x == r->ulhc_x) | 
 | 171 | 		bits |= W_PROTECT; | 
 | 172 | 	if (x == r->ulhc_x + r->width - 1) | 
 | 173 | 		bits |= E_PROTECT; | 
 | 174 | 	if (y == r->ulhc_y) | 
 | 175 | 		bits |= N_PROTECT; | 
 | 176 | 	if (y == r->ulhc_y + r->height - 1) | 
 | 177 | 		bits |= S_PROTECT; | 
 | 178 | 	BUG_ON(bits == 0); | 
 | 179 | 	__insn_mtspr(SPR_UDN_DIRECTION_PROTECT, bits); | 
 | 180 | 	enable_firewall_interrupts(); | 
 | 181 |  | 
 | 182 | } | 
 | 183 |  | 
 | 184 | /* Set up all cpus on edge of rectangle to enable/disable hardwall SPRs. */ | 
 | 185 | static void hardwall_setup(struct hardwall_info *r) | 
 | 186 | { | 
 | 187 | 	int x, y, cpu, delta; | 
 | 188 | 	struct cpumask rect_cpus; | 
 | 189 |  | 
 | 190 | 	cpumask_clear(&rect_cpus); | 
 | 191 |  | 
 | 192 | 	/* First include the top and bottom edges */ | 
 | 193 | 	cpu = r->ulhc_y * smp_width + r->ulhc_x; | 
 | 194 | 	delta = (r->height - 1) * smp_width; | 
 | 195 | 	for (x = 0; x < r->width; ++x, ++cpu) { | 
 | 196 | 		cpu_online_set(cpu, &rect_cpus); | 
 | 197 | 		cpu_online_set(cpu + delta, &rect_cpus); | 
 | 198 | 	} | 
 | 199 |  | 
 | 200 | 	/* Then the left and right edges */ | 
 | 201 | 	cpu -= r->width; | 
 | 202 | 	delta = r->width - 1; | 
 | 203 | 	for (y = 0; y < r->height; ++y, cpu += smp_width) { | 
 | 204 | 		cpu_online_set(cpu, &rect_cpus); | 
 | 205 | 		cpu_online_set(cpu + delta, &rect_cpus); | 
 | 206 | 	} | 
 | 207 |  | 
 | 208 | 	/* Then tell all the cpus to set up their protection SPR */ | 
 | 209 | 	on_each_cpu_mask(&rect_cpus, hardwall_setup_ipi_func, r, 1); | 
 | 210 | } | 
 | 211 |  | 
 | 212 | void __kprobes do_hardwall_trap(struct pt_regs* regs, int fault_num) | 
 | 213 | { | 
 | 214 | 	struct hardwall_info *rect; | 
 | 215 | 	struct task_struct *p; | 
 | 216 | 	struct siginfo info; | 
 | 217 | 	int x, y; | 
 | 218 | 	int cpu = smp_processor_id(); | 
 | 219 | 	int found_processes; | 
 | 220 | 	unsigned long flags; | 
 | 221 |  | 
 | 222 | 	struct pt_regs *old_regs = set_irq_regs(regs); | 
 | 223 | 	irq_enter(); | 
 | 224 |  | 
 | 225 | 	/* This tile trapped a network access; find the rectangle. */ | 
 | 226 | 	x = cpu % smp_width; | 
 | 227 | 	y = cpu / smp_width; | 
 | 228 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 229 | 	list_for_each_entry(rect, &rectangles, list) { | 
 | 230 | 		if (contains(rect, x, y)) | 
 | 231 | 			break; | 
 | 232 | 	} | 
 | 233 |  | 
 | 234 | 	/* | 
 | 235 | 	 * It shouldn't be possible not to find this cpu on the | 
 | 236 | 	 * rectangle list, since only cpus in rectangles get hardwalled. | 
 | 237 | 	 * The hardwall is only removed after the UDN is drained. | 
 | 238 | 	 */ | 
 | 239 | 	BUG_ON(&rect->list == &rectangles); | 
 | 240 |  | 
 | 241 | 	/* | 
 | 242 | 	 * If we already started teardown on this hardwall, don't worry; | 
 | 243 | 	 * the abort signal has been sent and we are just waiting for things | 
 | 244 | 	 * to quiesce. | 
 | 245 | 	 */ | 
 | 246 | 	if (rect->teardown_in_progress) { | 
 | 247 | 		pr_notice("cpu %d: detected hardwall violation %#lx" | 
 | 248 | 		       " while teardown already in progress\n", | 
 | 249 | 		       cpu, (long) __insn_mfspr(SPR_UDN_DIRECTION_PROTECT)); | 
 | 250 | 		goto done; | 
 | 251 | 	} | 
 | 252 |  | 
 | 253 | 	/* | 
 | 254 | 	 * Kill off any process that is activated in this rectangle. | 
 | 255 | 	 * We bypass security to deliver the signal, since it must be | 
 | 256 | 	 * one of the activated processes that generated the UDN | 
 | 257 | 	 * message that caused this trap, and all the activated | 
 | 258 | 	 * processes shared a single open file so are pretty tightly | 
 | 259 | 	 * bound together from a security point of view to begin with. | 
 | 260 | 	 */ | 
 | 261 | 	rect->teardown_in_progress = 1; | 
 | 262 | 	wmb(); /* Ensure visibility of rectangle before notifying processes. */ | 
 | 263 | 	pr_notice("cpu %d: detected hardwall violation %#lx...\n", | 
 | 264 | 	       cpu, (long) __insn_mfspr(SPR_UDN_DIRECTION_PROTECT)); | 
 | 265 | 	info.si_signo = SIGILL; | 
 | 266 | 	info.si_errno = 0; | 
 | 267 | 	info.si_code = ILL_HARDWALL; | 
 | 268 | 	found_processes = 0; | 
 | 269 | 	list_for_each_entry(p, &rect->task_head, thread.hardwall_list) { | 
 | 270 | 		BUG_ON(p->thread.hardwall != rect); | 
 | 271 | 		if (p->sighand) { | 
 | 272 | 			found_processes = 1; | 
 | 273 | 			pr_notice("hardwall: killing %d\n", p->pid); | 
 | 274 | 			spin_lock(&p->sighand->siglock); | 
 | 275 | 			__group_send_sig_info(info.si_signo, &info, p); | 
 | 276 | 			spin_unlock(&p->sighand->siglock); | 
 | 277 | 		} | 
 | 278 | 	} | 
 | 279 | 	if (!found_processes) | 
 | 280 | 		pr_notice("hardwall: no associated processes!\n"); | 
 | 281 |  | 
 | 282 |  done: | 
 | 283 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 284 |  | 
 | 285 | 	/* | 
 | 286 | 	 * We have to disable firewall interrupts now, or else when we | 
 | 287 | 	 * return from this handler, we will simply re-interrupt back to | 
 | 288 | 	 * it.  However, we can't clear the protection bits, since we | 
 | 289 | 	 * haven't yet drained the network, and that would allow packets | 
 | 290 | 	 * to cross out of the hardwall region. | 
 | 291 | 	 */ | 
 | 292 | 	disable_firewall_interrupts(); | 
 | 293 |  | 
 | 294 | 	irq_exit(); | 
 | 295 | 	set_irq_regs(old_regs); | 
 | 296 | } | 
 | 297 |  | 
 | 298 | /* Allow access from user space to the UDN. */ | 
 | 299 | void grant_network_mpls(void) | 
 | 300 | { | 
 | 301 | 	__insn_mtspr(SPR_MPL_UDN_ACCESS_SET_0, 1); | 
 | 302 | 	__insn_mtspr(SPR_MPL_UDN_AVAIL_SET_0, 1); | 
 | 303 | 	__insn_mtspr(SPR_MPL_UDN_COMPLETE_SET_0, 1); | 
 | 304 | 	__insn_mtspr(SPR_MPL_UDN_TIMER_SET_0, 1); | 
 | 305 | #if !CHIP_HAS_REV1_XDN() | 
 | 306 | 	__insn_mtspr(SPR_MPL_UDN_REFILL_SET_0, 1); | 
 | 307 | 	__insn_mtspr(SPR_MPL_UDN_CA_SET_0, 1); | 
 | 308 | #endif | 
 | 309 | } | 
 | 310 |  | 
 | 311 | /* Deny access from user space to the UDN. */ | 
 | 312 | void restrict_network_mpls(void) | 
 | 313 | { | 
 | 314 | 	__insn_mtspr(SPR_MPL_UDN_ACCESS_SET_1, 1); | 
 | 315 | 	__insn_mtspr(SPR_MPL_UDN_AVAIL_SET_1, 1); | 
 | 316 | 	__insn_mtspr(SPR_MPL_UDN_COMPLETE_SET_1, 1); | 
 | 317 | 	__insn_mtspr(SPR_MPL_UDN_TIMER_SET_1, 1); | 
 | 318 | #if !CHIP_HAS_REV1_XDN() | 
 | 319 | 	__insn_mtspr(SPR_MPL_UDN_REFILL_SET_1, 1); | 
 | 320 | 	__insn_mtspr(SPR_MPL_UDN_CA_SET_1, 1); | 
 | 321 | #endif | 
 | 322 | } | 
 | 323 |  | 
 | 324 |  | 
 | 325 | /* | 
 | 326 |  * Code to create, activate, deactivate, and destroy hardwall rectangles. | 
 | 327 |  */ | 
 | 328 |  | 
 | 329 | /* Create a hardwall for the given rectangle */ | 
 | 330 | static struct hardwall_info *hardwall_create( | 
 | 331 | 	size_t size, const unsigned char __user *bits) | 
 | 332 | { | 
 | 333 | 	struct hardwall_info *iter, *rect; | 
 | 334 | 	struct cpumask mask; | 
 | 335 | 	unsigned long flags; | 
 | 336 | 	int rc; | 
 | 337 |  | 
 | 338 | 	/* Reject crazy sizes out of hand, a la sys_mbind(). */ | 
 | 339 | 	if (size > PAGE_SIZE) | 
 | 340 | 		return ERR_PTR(-EINVAL); | 
 | 341 |  | 
 | 342 | 	/* Copy whatever fits into a cpumask. */ | 
 | 343 | 	if (copy_from_user(&mask, bits, min(sizeof(struct cpumask), size))) | 
 | 344 | 		return ERR_PTR(-EFAULT); | 
 | 345 |  | 
 | 346 | 	/* | 
 | 347 | 	 * If the size was short, clear the rest of the mask; | 
 | 348 | 	 * otherwise validate that the rest of the user mask was zero | 
 | 349 | 	 * (we don't try hard to be efficient when validating huge masks). | 
 | 350 | 	 */ | 
 | 351 | 	if (size < sizeof(struct cpumask)) { | 
 | 352 | 		memset((char *)&mask + size, 0, sizeof(struct cpumask) - size); | 
 | 353 | 	} else if (size > sizeof(struct cpumask)) { | 
 | 354 | 		size_t i; | 
 | 355 | 		for (i = sizeof(struct cpumask); i < size; ++i) { | 
 | 356 | 			char c; | 
 | 357 | 			if (get_user(c, &bits[i])) | 
 | 358 | 				return ERR_PTR(-EFAULT); | 
 | 359 | 			if (c) | 
 | 360 | 				return ERR_PTR(-EINVAL); | 
 | 361 | 		} | 
 | 362 | 	} | 
 | 363 |  | 
 | 364 | 	/* Allocate a new rectangle optimistically. */ | 
 | 365 | 	rect = kmalloc(sizeof(struct hardwall_info), | 
 | 366 | 			GFP_KERNEL | __GFP_ZERO); | 
| Kulikov Vasiliy | 1c689cb | 2010-07-16 20:13:02 +0400 | [diff] [blame] | 367 | 	if (rect == NULL) | 
 | 368 | 		return ERR_PTR(-ENOMEM); | 
| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 369 | 	INIT_LIST_HEAD(&rect->task_head); | 
 | 370 |  | 
 | 371 | 	/* Compute the rectangle size and validate that it's plausible. */ | 
 | 372 | 	rc = setup_rectangle(rect, &mask); | 
 | 373 | 	if (rc != 0) { | 
 | 374 | 		kfree(rect); | 
 | 375 | 		return ERR_PTR(rc); | 
 | 376 | 	} | 
 | 377 |  | 
 | 378 | 	/* Confirm it doesn't overlap and add it to the list. */ | 
 | 379 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 380 | 	list_for_each_entry(iter, &rectangles, list) { | 
 | 381 | 		if (overlaps(iter, rect)) { | 
 | 382 | 			spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 383 | 			kfree(rect); | 
 | 384 | 			return ERR_PTR(-EBUSY); | 
 | 385 | 		} | 
 | 386 | 	} | 
 | 387 | 	list_add_tail(&rect->list, &rectangles); | 
 | 388 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 389 |  | 
 | 390 | 	/* Set up appropriate hardwalling on all affected cpus. */ | 
 | 391 | 	hardwall_setup(rect); | 
 | 392 |  | 
 | 393 | 	return rect; | 
 | 394 | } | 
 | 395 |  | 
 | 396 | /* Activate a given hardwall on this cpu for this process. */ | 
 | 397 | static int hardwall_activate(struct hardwall_info *rect) | 
 | 398 | { | 
 | 399 | 	int cpu, x, y; | 
 | 400 | 	unsigned long flags; | 
 | 401 | 	struct task_struct *p = current; | 
 | 402 | 	struct thread_struct *ts = &p->thread; | 
 | 403 |  | 
 | 404 | 	/* Require a rectangle. */ | 
 | 405 | 	if (rect == NULL) | 
 | 406 | 		return -ENODATA; | 
 | 407 |  | 
 | 408 | 	/* Not allowed to activate a rectangle that is being torn down. */ | 
 | 409 | 	if (rect->teardown_in_progress) | 
 | 410 | 		return -EINVAL; | 
 | 411 |  | 
 | 412 | 	/* | 
 | 413 | 	 * Get our affinity; if we're not bound to this tile uniquely, | 
 | 414 | 	 * we can't access the network registers. | 
 | 415 | 	 */ | 
 | 416 | 	if (cpumask_weight(&p->cpus_allowed) != 1) | 
 | 417 | 		return -EPERM; | 
 | 418 |  | 
 | 419 | 	/* Make sure we are bound to a cpu in this rectangle. */ | 
 | 420 | 	cpu = smp_processor_id(); | 
 | 421 | 	BUG_ON(cpumask_first(&p->cpus_allowed) != cpu); | 
 | 422 | 	x = cpu_x(cpu); | 
 | 423 | 	y = cpu_y(cpu); | 
 | 424 | 	if (!contains(rect, x, y)) | 
 | 425 | 		return -EINVAL; | 
 | 426 |  | 
 | 427 | 	/* If we are already bound to this hardwall, it's a no-op. */ | 
 | 428 | 	if (ts->hardwall) { | 
 | 429 | 		BUG_ON(ts->hardwall != rect); | 
 | 430 | 		return 0; | 
 | 431 | 	} | 
 | 432 |  | 
 | 433 | 	/* Success!  This process gets to use the user networks on this cpu. */ | 
 | 434 | 	ts->hardwall = rect; | 
 | 435 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 436 | 	list_add(&ts->hardwall_list, &rect->task_head); | 
 | 437 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 438 | 	grant_network_mpls(); | 
 | 439 | 	printk(KERN_DEBUG "Pid %d (%s) activated for hardwall: cpu %d\n", | 
 | 440 | 	       p->pid, p->comm, cpu); | 
 | 441 | 	return 0; | 
 | 442 | } | 
 | 443 |  | 
 | 444 | /* | 
 | 445 |  * Deactivate a task's hardwall.  Must hold hardwall_lock. | 
 | 446 |  * This method may be called from free_task(), so we don't want to | 
 | 447 |  * rely on too many fields of struct task_struct still being valid. | 
 | 448 |  * We assume the cpus_allowed, pid, and comm fields are still valid. | 
 | 449 |  */ | 
 | 450 | static void _hardwall_deactivate(struct task_struct *task) | 
 | 451 | { | 
 | 452 | 	struct thread_struct *ts = &task->thread; | 
 | 453 |  | 
 | 454 | 	if (cpumask_weight(&task->cpus_allowed) != 1) { | 
 | 455 | 		pr_err("pid %d (%s) releasing networks with" | 
 | 456 | 		       " an affinity mask containing %d cpus!\n", | 
 | 457 | 		       task->pid, task->comm, | 
 | 458 | 		       cpumask_weight(&task->cpus_allowed)); | 
 | 459 | 		BUG(); | 
 | 460 | 	} | 
 | 461 |  | 
 | 462 | 	BUG_ON(ts->hardwall == NULL); | 
 | 463 | 	ts->hardwall = NULL; | 
 | 464 | 	list_del(&ts->hardwall_list); | 
 | 465 | 	if (task == current) | 
 | 466 | 		restrict_network_mpls(); | 
 | 467 | } | 
 | 468 |  | 
 | 469 | /* Deactivate a task's hardwall. */ | 
 | 470 | int hardwall_deactivate(struct task_struct *task) | 
 | 471 | { | 
 | 472 | 	unsigned long flags; | 
 | 473 | 	int activated; | 
 | 474 |  | 
 | 475 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 476 | 	activated = (task->thread.hardwall != NULL); | 
 | 477 | 	if (activated) | 
 | 478 | 		_hardwall_deactivate(task); | 
 | 479 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 480 |  | 
 | 481 | 	if (!activated) | 
 | 482 | 		return -EINVAL; | 
 | 483 |  | 
 | 484 | 	printk(KERN_DEBUG "Pid %d (%s) deactivated for hardwall: cpu %d\n", | 
 | 485 | 	       task->pid, task->comm, smp_processor_id()); | 
 | 486 | 	return 0; | 
 | 487 | } | 
 | 488 |  | 
 | 489 | /* Stop a UDN switch before draining the network. */ | 
 | 490 | static void stop_udn_switch(void *ignored) | 
 | 491 | { | 
 | 492 | #if !CHIP_HAS_REV1_XDN() | 
 | 493 | 	/* Freeze the switch and the demux. */ | 
 | 494 | 	__insn_mtspr(SPR_UDN_SP_FREEZE, | 
 | 495 | 		     SPR_UDN_SP_FREEZE__SP_FRZ_MASK | | 
 | 496 | 		     SPR_UDN_SP_FREEZE__DEMUX_FRZ_MASK | | 
 | 497 | 		     SPR_UDN_SP_FREEZE__NON_DEST_EXT_MASK); | 
 | 498 | #endif | 
 | 499 | } | 
 | 500 |  | 
 | 501 | /* Drain all the state from a stopped switch. */ | 
 | 502 | static void drain_udn_switch(void *ignored) | 
 | 503 | { | 
 | 504 | #if !CHIP_HAS_REV1_XDN() | 
 | 505 | 	int i; | 
 | 506 | 	int from_tile_words, ca_count; | 
 | 507 |  | 
 | 508 | 	/* Empty out the 5 switch point fifos. */ | 
 | 509 | 	for (i = 0; i < 5; i++) { | 
 | 510 | 		int words, j; | 
 | 511 | 		__insn_mtspr(SPR_UDN_SP_FIFO_SEL, i); | 
 | 512 | 		words = __insn_mfspr(SPR_UDN_SP_STATE) & 0xF; | 
 | 513 | 		for (j = 0; j < words; j++) | 
 | 514 | 			(void) __insn_mfspr(SPR_UDN_SP_FIFO_DATA); | 
 | 515 | 		BUG_ON((__insn_mfspr(SPR_UDN_SP_STATE) & 0xF) != 0); | 
 | 516 | 	} | 
 | 517 |  | 
 | 518 | 	/* Dump out the 3 word fifo at top. */ | 
 | 519 | 	from_tile_words = (__insn_mfspr(SPR_UDN_DEMUX_STATUS) >> 10) & 0x3; | 
 | 520 | 	for (i = 0; i < from_tile_words; i++) | 
 | 521 | 		(void) __insn_mfspr(SPR_UDN_DEMUX_WRITE_FIFO); | 
 | 522 |  | 
 | 523 | 	/* Empty out demuxes. */ | 
 | 524 | 	while (__insn_mfspr(SPR_UDN_DATA_AVAIL) & (1 << 0)) | 
 | 525 | 		(void) __tile_udn0_receive(); | 
 | 526 | 	while (__insn_mfspr(SPR_UDN_DATA_AVAIL) & (1 << 1)) | 
 | 527 | 		(void) __tile_udn1_receive(); | 
 | 528 | 	while (__insn_mfspr(SPR_UDN_DATA_AVAIL) & (1 << 2)) | 
 | 529 | 		(void) __tile_udn2_receive(); | 
 | 530 | 	while (__insn_mfspr(SPR_UDN_DATA_AVAIL) & (1 << 3)) | 
 | 531 | 		(void) __tile_udn3_receive(); | 
 | 532 | 	BUG_ON((__insn_mfspr(SPR_UDN_DATA_AVAIL) & 0xF) != 0); | 
 | 533 |  | 
 | 534 | 	/* Empty out catch all. */ | 
 | 535 | 	ca_count = __insn_mfspr(SPR_UDN_DEMUX_CA_COUNT); | 
 | 536 | 	for (i = 0; i < ca_count; i++) | 
 | 537 | 		(void) __insn_mfspr(SPR_UDN_CA_DATA); | 
 | 538 | 	BUG_ON(__insn_mfspr(SPR_UDN_DEMUX_CA_COUNT) != 0); | 
 | 539 |  | 
 | 540 | 	/* Clear demux logic. */ | 
 | 541 | 	__insn_mtspr(SPR_UDN_DEMUX_CTL, 1); | 
 | 542 |  | 
 | 543 | 	/* | 
 | 544 | 	 * Write switch state; experimentation indicates that 0xc3000 | 
 | 545 | 	 * is an idle switch point. | 
 | 546 | 	 */ | 
 | 547 | 	for (i = 0; i < 5; i++) { | 
 | 548 | 		__insn_mtspr(SPR_UDN_SP_FIFO_SEL, i); | 
 | 549 | 		__insn_mtspr(SPR_UDN_SP_STATE, 0xc3000); | 
 | 550 | 	} | 
 | 551 | #endif | 
 | 552 | } | 
 | 553 |  | 
 | 554 | /* Reset random UDN state registers at boot up and during hardwall teardown. */ | 
 | 555 | void reset_network_state(void) | 
 | 556 | { | 
 | 557 | #if !CHIP_HAS_REV1_XDN() | 
 | 558 | 	/* Reset UDN coordinates to their standard value */ | 
 | 559 | 	unsigned int cpu = smp_processor_id(); | 
 | 560 | 	unsigned int x = cpu % smp_width; | 
 | 561 | 	unsigned int y = cpu / smp_width; | 
 | 562 | #endif | 
 | 563 |  | 
 | 564 | 	if (udn_disabled) | 
 | 565 | 		return; | 
 | 566 |  | 
 | 567 | #if !CHIP_HAS_REV1_XDN() | 
 | 568 | 	__insn_mtspr(SPR_UDN_TILE_COORD, (x << 18) | (y << 7)); | 
 | 569 |  | 
 | 570 | 	/* Set demux tags to predefined values and enable them. */ | 
 | 571 | 	__insn_mtspr(SPR_UDN_TAG_VALID, 0xf); | 
 | 572 | 	__insn_mtspr(SPR_UDN_TAG_0, (1 << 0)); | 
 | 573 | 	__insn_mtspr(SPR_UDN_TAG_1, (1 << 1)); | 
 | 574 | 	__insn_mtspr(SPR_UDN_TAG_2, (1 << 2)); | 
 | 575 | 	__insn_mtspr(SPR_UDN_TAG_3, (1 << 3)); | 
 | 576 | #endif | 
 | 577 |  | 
 | 578 | 	/* Clear out other random registers so we have a clean slate. */ | 
 | 579 | 	__insn_mtspr(SPR_UDN_AVAIL_EN, 0); | 
 | 580 | 	__insn_mtspr(SPR_UDN_DEADLOCK_TIMEOUT, 0); | 
 | 581 | #if !CHIP_HAS_REV1_XDN() | 
 | 582 | 	__insn_mtspr(SPR_UDN_REFILL_EN, 0); | 
 | 583 | 	__insn_mtspr(SPR_UDN_DEMUX_QUEUE_SEL, 0); | 
 | 584 | 	__insn_mtspr(SPR_UDN_SP_FIFO_SEL, 0); | 
 | 585 | #endif | 
 | 586 |  | 
 | 587 | 	/* Start the switch and demux. */ | 
 | 588 | #if !CHIP_HAS_REV1_XDN() | 
 | 589 | 	__insn_mtspr(SPR_UDN_SP_FREEZE, 0); | 
 | 590 | #endif | 
 | 591 | } | 
 | 592 |  | 
 | 593 | /* Restart a UDN switch after draining. */ | 
 | 594 | static void restart_udn_switch(void *ignored) | 
 | 595 | { | 
 | 596 | 	reset_network_state(); | 
 | 597 |  | 
 | 598 | 	/* Disable firewall interrupts. */ | 
 | 599 | 	__insn_mtspr(SPR_UDN_DIRECTION_PROTECT, 0); | 
 | 600 | 	disable_firewall_interrupts(); | 
 | 601 | } | 
 | 602 |  | 
 | 603 | /* Build a struct cpumask containing all valid tiles in bounding rectangle. */ | 
 | 604 | static void fill_mask(struct hardwall_info *r, struct cpumask *result) | 
 | 605 | { | 
 | 606 | 	int x, y, cpu; | 
 | 607 |  | 
 | 608 | 	cpumask_clear(result); | 
 | 609 |  | 
 | 610 | 	cpu = r->ulhc_y * smp_width + r->ulhc_x; | 
 | 611 | 	for (y = 0; y < r->height; ++y, cpu += smp_width - r->width) { | 
 | 612 | 		for (x = 0; x < r->width; ++x, ++cpu) | 
 | 613 | 			cpu_online_set(cpu, result); | 
 | 614 | 	} | 
 | 615 | } | 
 | 616 |  | 
 | 617 | /* Last reference to a hardwall is gone, so clear the network. */ | 
 | 618 | static void hardwall_destroy(struct hardwall_info *rect) | 
 | 619 | { | 
 | 620 | 	struct task_struct *task; | 
 | 621 | 	unsigned long flags; | 
 | 622 | 	struct cpumask mask; | 
 | 623 |  | 
 | 624 | 	/* Make sure this file actually represents a rectangle. */ | 
 | 625 | 	if (rect == NULL) | 
 | 626 | 		return; | 
 | 627 |  | 
 | 628 | 	/* | 
 | 629 | 	 * Deactivate any remaining tasks.  It's possible to race with | 
 | 630 | 	 * some other thread that is exiting and hasn't yet called | 
 | 631 | 	 * deactivate (when freeing its thread_info), so we carefully | 
 | 632 | 	 * deactivate any remaining tasks before freeing the | 
 | 633 | 	 * hardwall_info object itself. | 
 | 634 | 	 */ | 
 | 635 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 636 | 	list_for_each_entry(task, &rect->task_head, thread.hardwall_list) | 
 | 637 | 		_hardwall_deactivate(task); | 
 | 638 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 639 |  | 
 | 640 | 	/* Drain the UDN. */ | 
 | 641 | 	printk(KERN_DEBUG "Clearing hardwall rectangle %dx%d %d,%d\n", | 
 | 642 | 	       rect->width, rect->height, rect->ulhc_x, rect->ulhc_y); | 
 | 643 | 	fill_mask(rect, &mask); | 
 | 644 | 	on_each_cpu_mask(&mask, stop_udn_switch, NULL, 1); | 
 | 645 | 	on_each_cpu_mask(&mask, drain_udn_switch, NULL, 1); | 
 | 646 |  | 
 | 647 | 	/* Restart switch and disable firewall. */ | 
 | 648 | 	on_each_cpu_mask(&mask, restart_udn_switch, NULL, 1); | 
 | 649 |  | 
 | 650 | 	/* Now free the rectangle from the list. */ | 
 | 651 | 	spin_lock_irqsave(&hardwall_lock, flags); | 
 | 652 | 	BUG_ON(!list_empty(&rect->task_head)); | 
 | 653 | 	list_del(&rect->list); | 
 | 654 | 	spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 655 | 	kfree(rect); | 
 | 656 | } | 
 | 657 |  | 
 | 658 |  | 
 | 659 | /* | 
 | 660 |  * Dump hardwall state via /proc; initialized in arch/tile/sys/proc.c. | 
 | 661 |  */ | 
 | 662 | int proc_tile_hardwall_show(struct seq_file *sf, void *v) | 
 | 663 | { | 
 | 664 | 	struct hardwall_info *r; | 
 | 665 |  | 
 | 666 | 	if (udn_disabled) { | 
 | 667 | 		seq_printf(sf, "%dx%d 0,0 pids:\n", smp_width, smp_height); | 
 | 668 | 		return 0; | 
 | 669 | 	} | 
 | 670 |  | 
 | 671 | 	spin_lock_irq(&hardwall_lock); | 
 | 672 | 	list_for_each_entry(r, &rectangles, list) { | 
 | 673 | 		struct task_struct *p; | 
 | 674 | 		seq_printf(sf, "%dx%d %d,%d pids:", | 
 | 675 | 			   r->width, r->height, r->ulhc_x, r->ulhc_y); | 
 | 676 | 		list_for_each_entry(p, &r->task_head, thread.hardwall_list) { | 
 | 677 | 			unsigned int cpu = cpumask_first(&p->cpus_allowed); | 
 | 678 | 			unsigned int x = cpu % smp_width; | 
 | 679 | 			unsigned int y = cpu / smp_width; | 
 | 680 | 			seq_printf(sf, " %d@%d,%d", p->pid, x, y); | 
 | 681 | 		} | 
 | 682 | 		seq_printf(sf, "\n"); | 
 | 683 | 	} | 
 | 684 | 	spin_unlock_irq(&hardwall_lock); | 
 | 685 | 	return 0; | 
 | 686 | } | 
 | 687 |  | 
 | 688 |  | 
 | 689 | /* | 
 | 690 |  * Character device support via ioctl/close. | 
 | 691 |  */ | 
 | 692 |  | 
 | 693 | static long hardwall_ioctl(struct file *file, unsigned int a, unsigned long b) | 
 | 694 | { | 
 | 695 | 	struct hardwall_info *rect = file->private_data; | 
 | 696 |  | 
 | 697 | 	if (_IOC_TYPE(a) != HARDWALL_IOCTL_BASE) | 
 | 698 | 		return -EINVAL; | 
 | 699 |  | 
 | 700 | 	switch (_IOC_NR(a)) { | 
 | 701 | 	case _HARDWALL_CREATE: | 
 | 702 | 		if (udn_disabled) | 
 | 703 | 			return -ENOSYS; | 
 | 704 | 		if (rect != NULL) | 
 | 705 | 			return -EALREADY; | 
 | 706 | 		rect = hardwall_create(_IOC_SIZE(a), | 
 | 707 | 					(const unsigned char __user *)b); | 
 | 708 | 		if (IS_ERR(rect)) | 
 | 709 | 			return PTR_ERR(rect); | 
 | 710 | 		file->private_data = rect; | 
 | 711 | 		return 0; | 
 | 712 |  | 
 | 713 | 	case _HARDWALL_ACTIVATE: | 
 | 714 | 		return hardwall_activate(rect); | 
 | 715 |  | 
 | 716 | 	case _HARDWALL_DEACTIVATE: | 
 | 717 | 		if (current->thread.hardwall != rect) | 
 | 718 | 			return -EINVAL; | 
 | 719 | 		return hardwall_deactivate(current); | 
 | 720 |  | 
 | 721 | 	default: | 
 | 722 | 		return -EINVAL; | 
 | 723 | 	} | 
 | 724 | } | 
 | 725 |  | 
 | 726 | #ifdef CONFIG_COMPAT | 
 | 727 | static long hardwall_compat_ioctl(struct file *file, | 
 | 728 | 				  unsigned int a, unsigned long b) | 
 | 729 | { | 
 | 730 | 	/* Sign-extend the argument so it can be used as a pointer. */ | 
 | 731 | 	return hardwall_ioctl(file, a, (unsigned long)compat_ptr(b)); | 
 | 732 | } | 
 | 733 | #endif | 
 | 734 |  | 
 | 735 | /* The user process closed the file; revoke access to user networks. */ | 
 | 736 | static int hardwall_flush(struct file *file, fl_owner_t owner) | 
 | 737 | { | 
 | 738 | 	struct hardwall_info *rect = file->private_data; | 
 | 739 | 	struct task_struct *task, *tmp; | 
 | 740 | 	unsigned long flags; | 
 | 741 |  | 
 | 742 | 	if (rect) { | 
 | 743 | 		/* | 
 | 744 | 		 * NOTE: if multiple threads are activated on this hardwall | 
 | 745 | 		 * file, the other threads will continue having access to the | 
 | 746 | 		 * UDN until they are context-switched out and back in again. | 
 | 747 | 		 * | 
 | 748 | 		 * NOTE: A NULL files pointer means the task is being torn | 
 | 749 | 		 * down, so in that case we also deactivate it. | 
 | 750 | 		 */ | 
 | 751 | 		spin_lock_irqsave(&hardwall_lock, flags); | 
 | 752 | 		list_for_each_entry_safe(task, tmp, &rect->task_head, | 
 | 753 | 					 thread.hardwall_list) { | 
 | 754 | 			if (task->files == owner || task->files == NULL) | 
 | 755 | 				_hardwall_deactivate(task); | 
 | 756 | 		} | 
 | 757 | 		spin_unlock_irqrestore(&hardwall_lock, flags); | 
 | 758 | 	} | 
 | 759 |  | 
 | 760 | 	return 0; | 
 | 761 | } | 
 | 762 |  | 
 | 763 | /* This hardwall is gone, so destroy it. */ | 
 | 764 | static int hardwall_release(struct inode *inode, struct file *file) | 
 | 765 | { | 
 | 766 | 	hardwall_destroy(file->private_data); | 
 | 767 | 	return 0; | 
 | 768 | } | 
 | 769 |  | 
 | 770 | static const struct file_operations dev_hardwall_fops = { | 
| Chris Metcalf | d02db4f | 2010-11-01 12:46:10 -0400 | [diff] [blame] | 771 | 	.open           = nonseekable_open, | 
| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 772 | 	.unlocked_ioctl = hardwall_ioctl, | 
 | 773 | #ifdef CONFIG_COMPAT | 
 | 774 | 	.compat_ioctl   = hardwall_compat_ioctl, | 
 | 775 | #endif | 
 | 776 | 	.flush          = hardwall_flush, | 
 | 777 | 	.release        = hardwall_release, | 
| Chris Metcalf | 9f9c038 | 2010-06-25 17:00:56 -0400 | [diff] [blame] | 778 | }; | 
 | 779 |  | 
 | 780 | static struct cdev hardwall_dev; | 
 | 781 |  | 
 | 782 | static int __init dev_hardwall_init(void) | 
 | 783 | { | 
 | 784 | 	int rc; | 
 | 785 | 	dev_t dev; | 
 | 786 |  | 
 | 787 | 	rc = alloc_chrdev_region(&dev, 0, 1, "hardwall"); | 
 | 788 | 	if (rc < 0) | 
 | 789 | 		return rc; | 
 | 790 | 	cdev_init(&hardwall_dev, &dev_hardwall_fops); | 
 | 791 | 	rc = cdev_add(&hardwall_dev, dev, 1); | 
 | 792 | 	if (rc < 0) | 
 | 793 | 		return rc; | 
 | 794 |  | 
 | 795 | 	return 0; | 
 | 796 | } | 
 | 797 | late_initcall(dev_hardwall_init); |