1 | /* |
2 | * This file is subject to the terms and conditions of the GNU General Public |
3 | * License. See the file "COPYING" in the main directory of this archive |
4 | * for more details. |
5 | * |
6 | * Copyright (C) 2004, 2005 MIPS Technologies, Inc. All rights reserved. |
7 | * Copyright (C) 2013 Imagination Technologies Ltd. |
8 | */ |
9 | #include <linux/kernel.h> |
10 | #include <linux/device.h> |
11 | #include <linux/fs.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/export.h> |
14 | |
15 | #include <asm/mipsregs.h> |
16 | #include <asm/mipsmtregs.h> |
17 | #include <asm/mips_mt.h> |
18 | #include <asm/vpe.h> |
19 | |
20 | static int major; |
21 | |
22 | /* The number of TCs and VPEs physically available on the core */ |
23 | static int hw_tcs, hw_vpes; |
24 | |
25 | /* We are prepared so configure and start the VPE... */ |
26 | int vpe_run(struct vpe *v) |
27 | { |
28 | unsigned long flags, val, dmt_flag; |
29 | struct vpe_notifications *notifier; |
30 | unsigned int vpeflags; |
31 | struct tc *t; |
32 | |
33 | /* check we are the Master VPE */ |
34 | local_irq_save(flags); |
35 | val = read_c0_vpeconf0(); |
36 | if (!(val & VPECONF0_MVP)) { |
37 | pr_warn("VPE loader: only Master VPE's are able to config MT\n" ); |
38 | local_irq_restore(flags); |
39 | |
40 | return -1; |
41 | } |
42 | |
43 | dmt_flag = dmt(); |
44 | vpeflags = dvpe(); |
45 | |
46 | if (list_empty(head: &v->tc)) { |
47 | evpe(vpeflags); |
48 | emt(dmt_flag); |
49 | local_irq_restore(flags); |
50 | |
51 | pr_warn("VPE loader: No TC's associated with VPE %d\n" , |
52 | v->minor); |
53 | |
54 | return -ENOEXEC; |
55 | } |
56 | |
57 | t = list_first_entry(&v->tc, struct tc, tc); |
58 | |
59 | /* Put MVPE's into 'configuration state' */ |
60 | set_c0_mvpcontrol(MVPCONTROL_VPC); |
61 | |
62 | settc(t->index); |
63 | |
64 | /* should check it is halted, and not activated */ |
65 | if ((read_tc_c0_tcstatus() & TCSTATUS_A) || |
66 | !(read_tc_c0_tchalt() & TCHALT_H)) { |
67 | evpe(vpeflags); |
68 | emt(dmt_flag); |
69 | local_irq_restore(flags); |
70 | |
71 | pr_warn("VPE loader: TC %d is already active!\n" , |
72 | t->index); |
73 | |
74 | return -ENOEXEC; |
75 | } |
76 | |
77 | /* |
78 | * Write the address we want it to start running from in the TCPC |
79 | * register. |
80 | */ |
81 | write_tc_c0_tcrestart((unsigned long)v->__start); |
82 | write_tc_c0_tccontext((unsigned long)0); |
83 | |
84 | /* |
85 | * Mark the TC as activated, not interrupt exempt and not dynamically |
86 | * allocatable |
87 | */ |
88 | val = read_tc_c0_tcstatus(); |
89 | val = (val & ~(TCSTATUS_DA | TCSTATUS_IXMT)) | TCSTATUS_A; |
90 | write_tc_c0_tcstatus(val); |
91 | |
92 | write_tc_c0_tchalt(read_tc_c0_tchalt() & ~TCHALT_H); |
93 | |
94 | /* |
95 | * We don't pass the memsize here, so VPE programs need to be |
96 | * compiled with DFLT_STACK_SIZE and DFLT_HEAP_SIZE defined. |
97 | */ |
98 | mttgpr(7, 0); |
99 | mttgpr(6, v->ntcs); |
100 | |
101 | /* set up VPE1 */ |
102 | /* |
103 | * bind the TC to VPE 1 as late as possible so we only have the final |
104 | * VPE registers to set up, and so an EJTAG probe can trigger on it |
105 | */ |
106 | write_tc_c0_tcbind((read_tc_c0_tcbind() & ~TCBIND_CURVPE) | 1); |
107 | |
108 | write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~(VPECONF0_VPA)); |
109 | |
110 | back_to_back_c0_hazard(); |
111 | |
112 | /* Set up the XTC bit in vpeconf0 to point at our tc */ |
113 | write_vpe_c0_vpeconf0((read_vpe_c0_vpeconf0() & ~(VPECONF0_XTC)) |
114 | | (t->index << VPECONF0_XTC_SHIFT)); |
115 | |
116 | back_to_back_c0_hazard(); |
117 | |
118 | /* enable this VPE */ |
119 | write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() | VPECONF0_VPA); |
120 | |
121 | /* clear out any left overs from a previous program */ |
122 | write_vpe_c0_status(0); |
123 | write_vpe_c0_cause(0); |
124 | |
125 | /* take system out of configuration state */ |
126 | clear_c0_mvpcontrol(MVPCONTROL_VPC); |
127 | |
128 | /* |
129 | * SMVP kernels manage VPE enable independently, but uniprocessor |
130 | * kernels need to turn it on, even if that wasn't the pre-dvpe() state. |
131 | */ |
132 | #ifdef CONFIG_SMP |
133 | evpe(vpeflags); |
134 | #else |
135 | evpe(EVPE_ENABLE); |
136 | #endif |
137 | emt(dmt_flag); |
138 | local_irq_restore(flags); |
139 | |
140 | list_for_each_entry(notifier, &v->notify, list) |
141 | notifier->start(VPE_MODULE_MINOR); |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | void cleanup_tc(struct tc *tc) |
147 | { |
148 | unsigned long flags; |
149 | unsigned int mtflags, vpflags; |
150 | int tmp; |
151 | |
152 | local_irq_save(flags); |
153 | mtflags = dmt(); |
154 | vpflags = dvpe(); |
155 | /* Put MVPE's into 'configuration state' */ |
156 | set_c0_mvpcontrol(MVPCONTROL_VPC); |
157 | |
158 | settc(tc->index); |
159 | tmp = read_tc_c0_tcstatus(); |
160 | |
161 | /* mark not allocated and not dynamically allocatable */ |
162 | tmp &= ~(TCSTATUS_A | TCSTATUS_DA); |
163 | tmp |= TCSTATUS_IXMT; /* interrupt exempt */ |
164 | write_tc_c0_tcstatus(tmp); |
165 | |
166 | write_tc_c0_tchalt(TCHALT_H); |
167 | mips_ihb(); |
168 | |
169 | clear_c0_mvpcontrol(MVPCONTROL_VPC); |
170 | evpe(vpflags); |
171 | emt(mtflags); |
172 | local_irq_restore(flags); |
173 | } |
174 | |
175 | /* module wrapper entry points */ |
176 | /* give me a vpe */ |
177 | void *vpe_alloc(void) |
178 | { |
179 | int i; |
180 | struct vpe *v; |
181 | |
182 | /* find a vpe */ |
183 | for (i = 1; i < MAX_VPES; i++) { |
184 | v = get_vpe(i); |
185 | if (v != NULL) { |
186 | v->state = VPE_STATE_INUSE; |
187 | return v; |
188 | } |
189 | } |
190 | return NULL; |
191 | } |
192 | EXPORT_SYMBOL(vpe_alloc); |
193 | |
194 | /* start running from here */ |
195 | int vpe_start(void *vpe, unsigned long start) |
196 | { |
197 | struct vpe *v = vpe; |
198 | |
199 | v->__start = start; |
200 | return vpe_run(v); |
201 | } |
202 | EXPORT_SYMBOL(vpe_start); |
203 | |
204 | /* halt it for now */ |
205 | int vpe_stop(void *vpe) |
206 | { |
207 | struct vpe *v = vpe; |
208 | struct tc *t; |
209 | unsigned int evpe_flags; |
210 | |
211 | evpe_flags = dvpe(); |
212 | |
213 | t = list_entry(v->tc.next, struct tc, tc); |
214 | if (t != NULL) { |
215 | settc(t->index); |
216 | write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); |
217 | } |
218 | |
219 | evpe(evpe_flags); |
220 | |
221 | return 0; |
222 | } |
223 | EXPORT_SYMBOL(vpe_stop); |
224 | |
225 | /* I've done with it thank you */ |
226 | int vpe_free(void *vpe) |
227 | { |
228 | struct vpe *v = vpe; |
229 | struct tc *t; |
230 | unsigned int evpe_flags; |
231 | |
232 | t = list_entry(v->tc.next, struct tc, tc); |
233 | if (t == NULL) |
234 | return -ENOEXEC; |
235 | |
236 | evpe_flags = dvpe(); |
237 | |
238 | /* Put MVPE's into 'configuration state' */ |
239 | set_c0_mvpcontrol(MVPCONTROL_VPC); |
240 | |
241 | settc(t->index); |
242 | write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); |
243 | |
244 | /* halt the TC */ |
245 | write_tc_c0_tchalt(TCHALT_H); |
246 | mips_ihb(); |
247 | |
248 | /* mark the TC unallocated */ |
249 | write_tc_c0_tcstatus(read_tc_c0_tcstatus() & ~TCSTATUS_A); |
250 | |
251 | v->state = VPE_STATE_UNUSED; |
252 | |
253 | clear_c0_mvpcontrol(MVPCONTROL_VPC); |
254 | evpe(evpe_flags); |
255 | |
256 | return 0; |
257 | } |
258 | EXPORT_SYMBOL(vpe_free); |
259 | |
260 | static ssize_t store_kill(struct device *dev, struct device_attribute *attr, |
261 | const char *buf, size_t len) |
262 | { |
263 | struct vpe *vpe = get_vpe(aprp_cpu_index()); |
264 | struct vpe_notifications *notifier; |
265 | |
266 | list_for_each_entry(notifier, &vpe->notify, list) |
267 | notifier->stop(aprp_cpu_index()); |
268 | |
269 | release_progmem(vpe->load_addr); |
270 | cleanup_tc(tc: get_tc(aprp_cpu_index())); |
271 | vpe_stop(vpe); |
272 | vpe_free(vpe); |
273 | |
274 | return len; |
275 | } |
276 | static DEVICE_ATTR(kill, S_IWUSR, NULL, store_kill); |
277 | |
278 | static ssize_t ntcs_show(struct device *cd, struct device_attribute *attr, |
279 | char *buf) |
280 | { |
281 | struct vpe *vpe = get_vpe(aprp_cpu_index()); |
282 | |
283 | return sprintf(buf, fmt: "%d\n" , vpe->ntcs); |
284 | } |
285 | |
286 | static ssize_t ntcs_store(struct device *dev, struct device_attribute *attr, |
287 | const char *buf, size_t len) |
288 | { |
289 | struct vpe *vpe = get_vpe(aprp_cpu_index()); |
290 | unsigned long new; |
291 | int ret; |
292 | |
293 | ret = kstrtoul(s: buf, base: 0, res: &new); |
294 | if (ret < 0) |
295 | return ret; |
296 | |
297 | if (new == 0 || new > (hw_tcs - aprp_cpu_index())) |
298 | return -EINVAL; |
299 | |
300 | vpe->ntcs = new; |
301 | |
302 | return len; |
303 | } |
304 | static DEVICE_ATTR_RW(ntcs); |
305 | |
306 | static struct attribute *vpe_attrs[] = { |
307 | &dev_attr_kill.attr, |
308 | &dev_attr_ntcs.attr, |
309 | NULL, |
310 | }; |
311 | ATTRIBUTE_GROUPS(vpe); |
312 | |
313 | static void vpe_device_release(struct device *cd) |
314 | { |
315 | } |
316 | |
317 | static struct class vpe_class = { |
318 | .name = "vpe" , |
319 | .dev_release = vpe_device_release, |
320 | .dev_groups = vpe_groups, |
321 | }; |
322 | |
323 | static struct device vpe_device; |
324 | |
325 | int __init vpe_module_init(void) |
326 | { |
327 | unsigned int mtflags, vpflags; |
328 | unsigned long flags, val; |
329 | struct vpe *v = NULL; |
330 | struct tc *t; |
331 | int tc, err; |
332 | |
333 | if (!cpu_has_mipsmt) { |
334 | pr_warn("VPE loader: not a MIPS MT capable processor\n" ); |
335 | return -ENODEV; |
336 | } |
337 | |
338 | if (vpelimit == 0) { |
339 | pr_warn("No VPEs reserved for AP/SP, not initialize VPE loader\n" |
340 | "Pass maxvpes=<n> argument as kernel argument\n" ); |
341 | |
342 | return -ENODEV; |
343 | } |
344 | |
345 | if (aprp_cpu_index() == 0) { |
346 | pr_warn("No TCs reserved for AP/SP, not initialize VPE loader\n" |
347 | "Pass maxtcs=<n> argument as kernel argument\n" ); |
348 | |
349 | return -ENODEV; |
350 | } |
351 | |
352 | major = register_chrdev(0, VPE_MODULE_NAME, &vpe_fops); |
353 | if (major < 0) { |
354 | pr_warn("VPE loader: unable to register character device\n" ); |
355 | return major; |
356 | } |
357 | |
358 | err = class_register(class: &vpe_class); |
359 | if (err) { |
360 | pr_err("vpe_class registration failed\n" ); |
361 | goto out_chrdev; |
362 | } |
363 | |
364 | device_initialize(dev: &vpe_device); |
365 | vpe_device.class = &vpe_class; |
366 | vpe_device.parent = NULL; |
367 | dev_set_name(dev: &vpe_device, name: "vpe1" ); |
368 | vpe_device.devt = MKDEV(major, VPE_MODULE_MINOR); |
369 | err = device_add(dev: &vpe_device); |
370 | if (err) { |
371 | pr_err("Adding vpe_device failed\n" ); |
372 | goto out_class; |
373 | } |
374 | |
375 | local_irq_save(flags); |
376 | mtflags = dmt(); |
377 | vpflags = dvpe(); |
378 | |
379 | /* Put MVPE's into 'configuration state' */ |
380 | set_c0_mvpcontrol(MVPCONTROL_VPC); |
381 | |
382 | val = read_c0_mvpconf0(); |
383 | hw_tcs = (val & MVPCONF0_PTC) + 1; |
384 | hw_vpes = ((val & MVPCONF0_PVPE) >> MVPCONF0_PVPE_SHIFT) + 1; |
385 | |
386 | for (tc = aprp_cpu_index(); tc < hw_tcs; tc++) { |
387 | /* |
388 | * Must re-enable multithreading temporarily or in case we |
389 | * reschedule send IPIs or similar we might hang. |
390 | */ |
391 | clear_c0_mvpcontrol(MVPCONTROL_VPC); |
392 | evpe(vpflags); |
393 | emt(mtflags); |
394 | local_irq_restore(flags); |
395 | t = alloc_tc(tc); |
396 | if (!t) { |
397 | err = -ENOMEM; |
398 | goto out_dev; |
399 | } |
400 | |
401 | local_irq_save(flags); |
402 | mtflags = dmt(); |
403 | vpflags = dvpe(); |
404 | set_c0_mvpcontrol(MVPCONTROL_VPC); |
405 | |
406 | /* VPE's */ |
407 | if (tc < hw_tcs) { |
408 | settc(tc); |
409 | |
410 | v = alloc_vpe(tc); |
411 | if (v == NULL) { |
412 | pr_warn("VPE: unable to allocate VPE\n" ); |
413 | goto out_reenable; |
414 | } |
415 | |
416 | v->ntcs = hw_tcs - aprp_cpu_index(); |
417 | |
418 | /* add the tc to the list of this vpe's tc's. */ |
419 | list_add(new: &t->tc, head: &v->tc); |
420 | |
421 | /* deactivate all but vpe0 */ |
422 | if (tc >= aprp_cpu_index()) { |
423 | unsigned long tmp = read_vpe_c0_vpeconf0(); |
424 | |
425 | tmp &= ~VPECONF0_VPA; |
426 | |
427 | /* master VPE */ |
428 | tmp |= VPECONF0_MVP; |
429 | write_vpe_c0_vpeconf0(tmp); |
430 | } |
431 | |
432 | /* disable multi-threading with TC's */ |
433 | write_vpe_c0_vpecontrol(read_vpe_c0_vpecontrol() & |
434 | ~VPECONTROL_TE); |
435 | |
436 | if (tc >= vpelimit) { |
437 | /* |
438 | * Set config to be the same as vpe0, |
439 | * particularly kseg0 coherency alg |
440 | */ |
441 | write_vpe_c0_config(read_c0_config()); |
442 | } |
443 | } |
444 | |
445 | /* TC's */ |
446 | t->pvpe = v; /* set the parent vpe */ |
447 | |
448 | if (tc >= aprp_cpu_index()) { |
449 | unsigned long tmp; |
450 | |
451 | settc(tc); |
452 | |
453 | /* |
454 | * A TC that is bound to any other VPE gets bound to |
455 | * VPE0, ideally I'd like to make it homeless but it |
456 | * doesn't appear to let me bind a TC to a non-existent |
457 | * VPE. Which is perfectly reasonable. |
458 | * |
459 | * The (un)bound state is visible to an EJTAG probe so |
460 | * may notify GDB... |
461 | */ |
462 | tmp = read_tc_c0_tcbind(); |
463 | if (tmp & TCBIND_CURVPE) { |
464 | /* tc is bound >vpe0 */ |
465 | write_tc_c0_tcbind(tmp & ~TCBIND_CURVPE); |
466 | |
467 | t->pvpe = get_vpe(0); /* set the parent vpe */ |
468 | } |
469 | |
470 | /* halt the TC */ |
471 | write_tc_c0_tchalt(TCHALT_H); |
472 | mips_ihb(); |
473 | |
474 | tmp = read_tc_c0_tcstatus(); |
475 | |
476 | /* mark not activated and not dynamically allocatable */ |
477 | tmp &= ~(TCSTATUS_A | TCSTATUS_DA); |
478 | tmp |= TCSTATUS_IXMT; /* interrupt exempt */ |
479 | write_tc_c0_tcstatus(tmp); |
480 | } |
481 | } |
482 | |
483 | out_reenable: |
484 | /* release config state */ |
485 | clear_c0_mvpcontrol(MVPCONTROL_VPC); |
486 | |
487 | evpe(vpflags); |
488 | emt(mtflags); |
489 | local_irq_restore(flags); |
490 | |
491 | return 0; |
492 | |
493 | out_dev: |
494 | device_del(dev: &vpe_device); |
495 | |
496 | out_class: |
497 | put_device(dev: &vpe_device); |
498 | class_unregister(class: &vpe_class); |
499 | |
500 | out_chrdev: |
501 | unregister_chrdev(major, VPE_MODULE_NAME); |
502 | |
503 | return err; |
504 | } |
505 | |
506 | void __exit vpe_module_exit(void) |
507 | { |
508 | struct vpe *v, *n; |
509 | |
510 | device_unregister(dev: &vpe_device); |
511 | class_unregister(class: &vpe_class); |
512 | unregister_chrdev(major, VPE_MODULE_NAME); |
513 | |
514 | /* No locking needed here */ |
515 | list_for_each_entry_safe(v, n, &vpecontrol.vpe_list, list) { |
516 | if (v->state != VPE_STATE_UNUSED) |
517 | release_vpe(v); |
518 | } |
519 | } |
520 | |