Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 1 | /* |
| 2 | * MMU context allocation for 64-bit kernels. |
| 3 | * |
| 4 | * Copyright (C) 2004 Anton Blanchard, IBM Corp. <anton@samba.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU General Public License |
| 8 | * as published by the Free Software Foundation; either version |
| 9 | * 2 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | */ |
| 12 | |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 13 | #include <linux/sched.h> |
| 14 | #include <linux/kernel.h> |
| 15 | #include <linux/errno.h> |
| 16 | #include <linux/string.h> |
| 17 | #include <linux/types.h> |
| 18 | #include <linux/mm.h> |
| 19 | #include <linux/spinlock.h> |
| 20 | #include <linux/idr.h> |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 21 | #include <linux/module.h> |
Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 22 | #include <linux/gfp.h> |
Tseng-Hui (Frank) Lin | 851d2e2 | 2011-05-02 20:43:04 +0000 | [diff] [blame] | 23 | #include <linux/slab.h> |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 24 | |
| 25 | #include <asm/mmu_context.h> |
| 26 | |
Tseng-Hui (Frank) Lin | 851d2e2 | 2011-05-02 20:43:04 +0000 | [diff] [blame] | 27 | #ifdef CONFIG_PPC_ICSWX |
| 28 | /* |
| 29 | * The processor and its L2 cache cause the icswx instruction to |
| 30 | * generate a COP_REQ transaction on PowerBus. The transaction has |
| 31 | * no address, and the processor does not perform an MMU access |
| 32 | * to authenticate the transaction. The command portion of the |
| 33 | * PowerBus COP_REQ transaction includes the LPAR_ID (LPID) and |
| 34 | * the coprocessor Process ID (PID), which the coprocessor compares |
| 35 | * to the authorized LPID and PID held in the coprocessor, to determine |
| 36 | * if the process is authorized to generate the transaction. |
| 37 | * The data of the COP_REQ transaction is 128-byte or less and is |
| 38 | * placed in cacheable memory on a 128-byte cache line boundary. |
| 39 | * |
| 40 | * The task to use a coprocessor should use use_cop() to allocate |
| 41 | * a coprocessor PID before executing icswx instruction. use_cop() |
| 42 | * also enables the coprocessor context switching. Drop_cop() is |
| 43 | * used to free the coprocessor PID. |
| 44 | * |
| 45 | * Example: |
| 46 | * Host Fabric Interface (HFI) is a PowerPC network coprocessor. |
| 47 | * Each HFI have multiple windows. Each HFI window serves as a |
| 48 | * network device sending to and receiving from HFI network. |
| 49 | * HFI immediate send function uses icswx instruction. The immediate |
| 50 | * send function allows small (single cache-line) packets be sent |
| 51 | * without using the regular HFI send FIFO and doorbell, which are |
| 52 | * much slower than immediate send. |
| 53 | * |
| 54 | * For each task intending to use HFI immediate send, the HFI driver |
| 55 | * calls use_cop() to obtain a coprocessor PID for the task. |
| 56 | * The HFI driver then allocate a free HFI window and save the |
| 57 | * coprocessor PID to the HFI window to allow the task to use the |
| 58 | * HFI window. |
| 59 | * |
| 60 | * The HFI driver repeatedly creates immediate send packets and |
| 61 | * issues icswx instruction to send data through the HFI window. |
| 62 | * The HFI compares the coprocessor PID in the CPU PID register |
| 63 | * to the PID held in the HFI window to determine if the transaction |
| 64 | * is allowed. |
| 65 | * |
| 66 | * When the task to release the HFI window, the HFI driver calls |
| 67 | * drop_cop() to release the coprocessor PID. |
| 68 | */ |
| 69 | |
| 70 | #define COP_PID_NONE 0 |
| 71 | #define COP_PID_MIN (COP_PID_NONE + 1) |
| 72 | #define COP_PID_MAX (0xFFFF) |
| 73 | |
| 74 | static DEFINE_SPINLOCK(mmu_context_acop_lock); |
| 75 | static DEFINE_IDA(cop_ida); |
| 76 | |
| 77 | void switch_cop(struct mm_struct *next) |
| 78 | { |
| 79 | mtspr(SPRN_PID, next->context.cop_pid); |
| 80 | mtspr(SPRN_ACOP, next->context.acop); |
| 81 | } |
| 82 | |
| 83 | static int new_cop_pid(struct ida *ida, int min_id, int max_id, |
| 84 | spinlock_t *lock) |
| 85 | { |
| 86 | int index; |
| 87 | int err; |
| 88 | |
| 89 | again: |
| 90 | if (!ida_pre_get(ida, GFP_KERNEL)) |
| 91 | return -ENOMEM; |
| 92 | |
| 93 | spin_lock(lock); |
| 94 | err = ida_get_new_above(ida, min_id, &index); |
| 95 | spin_unlock(lock); |
| 96 | |
| 97 | if (err == -EAGAIN) |
| 98 | goto again; |
| 99 | else if (err) |
| 100 | return err; |
| 101 | |
| 102 | if (index > max_id) { |
| 103 | spin_lock(lock); |
| 104 | ida_remove(ida, index); |
| 105 | spin_unlock(lock); |
| 106 | return -ENOMEM; |
| 107 | } |
| 108 | |
| 109 | return index; |
| 110 | } |
| 111 | |
| 112 | static void sync_cop(void *arg) |
| 113 | { |
| 114 | struct mm_struct *mm = arg; |
| 115 | |
| 116 | if (mm == current->active_mm) |
| 117 | switch_cop(current->active_mm); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Start using a coprocessor. |
| 122 | * @acop: mask of coprocessor to be used. |
| 123 | * @mm: The mm the coprocessor to associate with. Most likely current mm. |
| 124 | * |
| 125 | * Return a positive PID if successful. Negative errno otherwise. |
| 126 | * The returned PID will be fed to the coprocessor to determine if an |
| 127 | * icswx transaction is authenticated. |
| 128 | */ |
| 129 | int use_cop(unsigned long acop, struct mm_struct *mm) |
| 130 | { |
| 131 | int ret; |
| 132 | |
| 133 | if (!cpu_has_feature(CPU_FTR_ICSWX)) |
| 134 | return -ENODEV; |
| 135 | |
| 136 | if (!mm || !acop) |
| 137 | return -EINVAL; |
| 138 | |
| 139 | /* We need to make sure mm_users doesn't change */ |
| 140 | down_read(&mm->mmap_sem); |
| 141 | spin_lock(mm->context.cop_lockp); |
| 142 | |
| 143 | if (mm->context.cop_pid == COP_PID_NONE) { |
| 144 | ret = new_cop_pid(&cop_ida, COP_PID_MIN, COP_PID_MAX, |
| 145 | &mmu_context_acop_lock); |
| 146 | if (ret < 0) |
| 147 | goto out; |
| 148 | |
| 149 | mm->context.cop_pid = ret; |
| 150 | } |
| 151 | mm->context.acop |= acop; |
| 152 | |
| 153 | sync_cop(mm); |
| 154 | |
| 155 | /* |
| 156 | * If this is a threaded process then there might be other threads |
| 157 | * running. We need to send an IPI to force them to pick up any |
| 158 | * change in PID and ACOP. |
| 159 | */ |
| 160 | if (atomic_read(&mm->mm_users) > 1) |
| 161 | smp_call_function(sync_cop, mm, 1); |
| 162 | |
| 163 | ret = mm->context.cop_pid; |
| 164 | |
| 165 | out: |
| 166 | spin_unlock(mm->context.cop_lockp); |
| 167 | up_read(&mm->mmap_sem); |
| 168 | |
| 169 | return ret; |
| 170 | } |
| 171 | EXPORT_SYMBOL_GPL(use_cop); |
| 172 | |
| 173 | /** |
| 174 | * Stop using a coprocessor. |
| 175 | * @acop: mask of coprocessor to be stopped. |
| 176 | * @mm: The mm the coprocessor associated with. |
| 177 | */ |
| 178 | void drop_cop(unsigned long acop, struct mm_struct *mm) |
| 179 | { |
| 180 | int free_pid = COP_PID_NONE; |
| 181 | |
| 182 | if (!cpu_has_feature(CPU_FTR_ICSWX)) |
| 183 | return; |
| 184 | |
| 185 | if (WARN_ON_ONCE(!mm)) |
| 186 | return; |
| 187 | |
| 188 | /* We need to make sure mm_users doesn't change */ |
| 189 | down_read(&mm->mmap_sem); |
| 190 | spin_lock(mm->context.cop_lockp); |
| 191 | |
| 192 | mm->context.acop &= ~acop; |
| 193 | |
| 194 | if ((!mm->context.acop) && (mm->context.cop_pid != COP_PID_NONE)) { |
| 195 | free_pid = mm->context.cop_pid; |
| 196 | mm->context.cop_pid = COP_PID_NONE; |
| 197 | } |
| 198 | |
| 199 | sync_cop(mm); |
| 200 | |
| 201 | /* |
| 202 | * If this is a threaded process then there might be other threads |
| 203 | * running. We need to send an IPI to force them to pick up any |
| 204 | * change in PID and ACOP. |
| 205 | */ |
| 206 | if (atomic_read(&mm->mm_users) > 1) |
| 207 | smp_call_function(sync_cop, mm, 1); |
| 208 | |
| 209 | if (free_pid != COP_PID_NONE) { |
| 210 | spin_lock(&mmu_context_acop_lock); |
| 211 | ida_remove(&cop_ida, free_pid); |
| 212 | spin_unlock(&mmu_context_acop_lock); |
| 213 | } |
| 214 | |
| 215 | spin_unlock(mm->context.cop_lockp); |
| 216 | up_read(&mm->mmap_sem); |
| 217 | } |
| 218 | EXPORT_SYMBOL_GPL(drop_cop); |
| 219 | |
| 220 | #endif /* CONFIG_PPC_ICSWX */ |
| 221 | |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 222 | static DEFINE_SPINLOCK(mmu_context_lock); |
Anton Blanchard | 7317ac8 | 2010-02-07 12:30:12 +0000 | [diff] [blame] | 223 | static DEFINE_IDA(mmu_context_ida); |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 224 | |
Benjamin Herrenschmidt | 5e69661 | 2008-12-18 19:13:24 +0000 | [diff] [blame] | 225 | /* |
| 226 | * The proto-VSID space has 2^35 - 1 segments available for user mappings. |
| 227 | * Each segment contains 2^28 bytes. Each context maps 2^44 bytes, |
| 228 | * so we can support 2^19-1 contexts (19 == 35 + 28 - 44). |
| 229 | */ |
Benjamin Herrenschmidt | 5e69661 | 2008-12-18 19:13:24 +0000 | [diff] [blame] | 230 | #define MAX_CONTEXT ((1UL << 19) - 1) |
| 231 | |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 232 | int __init_new_context(void) |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 233 | { |
| 234 | int index; |
| 235 | int err; |
| 236 | |
| 237 | again: |
Anton Blanchard | 7317ac8 | 2010-02-07 12:30:12 +0000 | [diff] [blame] | 238 | if (!ida_pre_get(&mmu_context_ida, GFP_KERNEL)) |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 239 | return -ENOMEM; |
| 240 | |
| 241 | spin_lock(&mmu_context_lock); |
Anton Blanchard | 7317ac8 | 2010-02-07 12:30:12 +0000 | [diff] [blame] | 242 | err = ida_get_new_above(&mmu_context_ida, 1, &index); |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 243 | spin_unlock(&mmu_context_lock); |
| 244 | |
| 245 | if (err == -EAGAIN) |
| 246 | goto again; |
| 247 | else if (err) |
| 248 | return err; |
| 249 | |
| 250 | if (index > MAX_CONTEXT) { |
Sonny Rao | f86c9747 | 2006-06-27 08:46:09 -0400 | [diff] [blame] | 251 | spin_lock(&mmu_context_lock); |
Anton Blanchard | 7317ac8 | 2010-02-07 12:30:12 +0000 | [diff] [blame] | 252 | ida_remove(&mmu_context_ida, index); |
Sonny Rao | f86c9747 | 2006-06-27 08:46:09 -0400 | [diff] [blame] | 253 | spin_unlock(&mmu_context_lock); |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 254 | return -ENOMEM; |
| 255 | } |
| 256 | |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 257 | return index; |
| 258 | } |
| 259 | EXPORT_SYMBOL_GPL(__init_new_context); |
| 260 | |
| 261 | int init_new_context(struct task_struct *tsk, struct mm_struct *mm) |
| 262 | { |
| 263 | int index; |
| 264 | |
| 265 | index = __init_new_context(); |
| 266 | if (index < 0) |
| 267 | return index; |
| 268 | |
Benjamin Herrenschmidt | d0f13e3 | 2007-05-08 16:27:27 +1000 | [diff] [blame] | 269 | /* The old code would re-promote on fork, we don't do that |
| 270 | * when using slices as it could cause problem promoting slices |
| 271 | * that have been forced down to 4K |
| 272 | */ |
Stephen Rothwell | e8ff064 | 2007-08-15 16:51:18 +1000 | [diff] [blame] | 273 | if (slice_mm_new_context(mm)) |
Benjamin Herrenschmidt | d0f13e3 | 2007-05-08 16:27:27 +1000 | [diff] [blame] | 274 | slice_set_user_psize(mm, mmu_virtual_psize); |
David Gibson | d28513b | 2009-11-26 18:56:04 +0000 | [diff] [blame] | 275 | subpage_prot_init_new_context(mm); |
Stephen Rothwell | 9dfe5c53 | 2007-08-15 16:33:55 +1000 | [diff] [blame] | 276 | mm->context.id = index; |
Tseng-Hui (Frank) Lin | 851d2e2 | 2011-05-02 20:43:04 +0000 | [diff] [blame] | 277 | #ifdef CONFIG_PPC_ICSWX |
| 278 | mm->context.cop_lockp = kmalloc(sizeof(spinlock_t), GFP_KERNEL); |
| 279 | if (!mm->context.cop_lockp) { |
| 280 | __destroy_context(index); |
| 281 | subpage_prot_free(mm); |
Stephen Rothwell | 79af218 | 2011-05-06 10:39:08 +1000 | [diff] [blame] | 282 | mm->context.id = MMU_NO_CONTEXT; |
Tseng-Hui (Frank) Lin | 851d2e2 | 2011-05-02 20:43:04 +0000 | [diff] [blame] | 283 | return -ENOMEM; |
| 284 | } |
| 285 | spin_lock_init(mm->context.cop_lockp); |
| 286 | #endif /* CONFIG_PPC_ICSWX */ |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 287 | |
| 288 | return 0; |
| 289 | } |
| 290 | |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 291 | void __destroy_context(int context_id) |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 292 | { |
| 293 | spin_lock(&mmu_context_lock); |
Anton Blanchard | 7317ac8 | 2010-02-07 12:30:12 +0000 | [diff] [blame] | 294 | ida_remove(&mmu_context_ida, context_id); |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 295 | spin_unlock(&mmu_context_lock); |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 296 | } |
| 297 | EXPORT_SYMBOL_GPL(__destroy_context); |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 298 | |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 299 | void destroy_context(struct mm_struct *mm) |
| 300 | { |
Tseng-Hui (Frank) Lin | 851d2e2 | 2011-05-02 20:43:04 +0000 | [diff] [blame] | 301 | #ifdef CONFIG_PPC_ICSWX |
| 302 | drop_cop(mm->context.acop, mm); |
| 303 | kfree(mm->context.cop_lockp); |
| 304 | mm->context.cop_lockp = NULL; |
| 305 | #endif /* CONFIG_PPC_ICSWX */ |
Alexander Graf | e85a471 | 2009-11-02 12:02:30 +0000 | [diff] [blame] | 306 | __destroy_context(mm->context.id); |
David Gibson | d28513b | 2009-11-26 18:56:04 +0000 | [diff] [blame] | 307 | subpage_prot_free(mm); |
Michael Ellerman | 5e8e7b4 | 2011-04-12 19:00:04 +0000 | [diff] [blame] | 308 | mm->context.id = MMU_NO_CONTEXT; |
Paul Mackerras | 14cf11a | 2005-09-26 16:04:21 +1000 | [diff] [blame] | 309 | } |