mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
11 KiB
521 lines
11 KiB
/* |
|
* This file is subject to the terms and conditions of the GNU General Public |
|
* License. See the file "COPYING" in the main directory of this archive |
|
* for more details. |
|
* |
|
* Copyright (C) 2004, 2005 MIPS Technologies, Inc. All rights reserved. |
|
* Copyright (C) 2013 Imagination Technologies Ltd. |
|
*/ |
|
#include <linux/kernel.h> |
|
#include <linux/device.h> |
|
#include <linux/fs.h> |
|
#include <linux/slab.h> |
|
#include <linux/export.h> |
|
|
|
#include <asm/mipsregs.h> |
|
#include <asm/mipsmtregs.h> |
|
#include <asm/mips_mt.h> |
|
#include <asm/vpe.h> |
|
|
|
static int major; |
|
|
|
/* The number of TCs and VPEs physically available on the core */ |
|
static int hw_tcs, hw_vpes; |
|
|
|
/* We are prepared so configure and start the VPE... */ |
|
int vpe_run(struct vpe *v) |
|
{ |
|
unsigned long flags, val, dmt_flag; |
|
struct vpe_notifications *notifier; |
|
unsigned int vpeflags; |
|
struct tc *t; |
|
|
|
/* check we are the Master VPE */ |
|
local_irq_save(flags); |
|
val = read_c0_vpeconf0(); |
|
if (!(val & VPECONF0_MVP)) { |
|
pr_warn("VPE loader: only Master VPE's are able to config MT\n"); |
|
local_irq_restore(flags); |
|
|
|
return -1; |
|
} |
|
|
|
dmt_flag = dmt(); |
|
vpeflags = dvpe(); |
|
|
|
if (list_empty(&v->tc)) { |
|
evpe(vpeflags); |
|
emt(dmt_flag); |
|
local_irq_restore(flags); |
|
|
|
pr_warn("VPE loader: No TC's associated with VPE %d\n", |
|
v->minor); |
|
|
|
return -ENOEXEC; |
|
} |
|
|
|
t = list_first_entry(&v->tc, struct tc, tc); |
|
|
|
/* Put MVPE's into 'configuration state' */ |
|
set_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
settc(t->index); |
|
|
|
/* should check it is halted, and not activated */ |
|
if ((read_tc_c0_tcstatus() & TCSTATUS_A) || |
|
!(read_tc_c0_tchalt() & TCHALT_H)) { |
|
evpe(vpeflags); |
|
emt(dmt_flag); |
|
local_irq_restore(flags); |
|
|
|
pr_warn("VPE loader: TC %d is already active!\n", |
|
t->index); |
|
|
|
return -ENOEXEC; |
|
} |
|
|
|
/* |
|
* Write the address we want it to start running from in the TCPC |
|
* register. |
|
*/ |
|
write_tc_c0_tcrestart((unsigned long)v->__start); |
|
write_tc_c0_tccontext((unsigned long)0); |
|
|
|
/* |
|
* Mark the TC as activated, not interrupt exempt and not dynamically |
|
* allocatable |
|
*/ |
|
val = read_tc_c0_tcstatus(); |
|
val = (val & ~(TCSTATUS_DA | TCSTATUS_IXMT)) | TCSTATUS_A; |
|
write_tc_c0_tcstatus(val); |
|
|
|
write_tc_c0_tchalt(read_tc_c0_tchalt() & ~TCHALT_H); |
|
|
|
/* |
|
* The sde-kit passes 'memsize' to __start in $a3, so set something |
|
* here... Or set $a3 to zero and define DFLT_STACK_SIZE and |
|
* DFLT_HEAP_SIZE when you compile your program |
|
*/ |
|
mttgpr(6, v->ntcs); |
|
mttgpr(7, physical_memsize); |
|
|
|
/* set up VPE1 */ |
|
/* |
|
* bind the TC to VPE 1 as late as possible so we only have the final |
|
* VPE registers to set up, and so an EJTAG probe can trigger on it |
|
*/ |
|
write_tc_c0_tcbind((read_tc_c0_tcbind() & ~TCBIND_CURVPE) | 1); |
|
|
|
write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~(VPECONF0_VPA)); |
|
|
|
back_to_back_c0_hazard(); |
|
|
|
/* Set up the XTC bit in vpeconf0 to point at our tc */ |
|
write_vpe_c0_vpeconf0((read_vpe_c0_vpeconf0() & ~(VPECONF0_XTC)) |
|
| (t->index << VPECONF0_XTC_SHIFT)); |
|
|
|
back_to_back_c0_hazard(); |
|
|
|
/* enable this VPE */ |
|
write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() | VPECONF0_VPA); |
|
|
|
/* clear out any left overs from a previous program */ |
|
write_vpe_c0_status(0); |
|
write_vpe_c0_cause(0); |
|
|
|
/* take system out of configuration state */ |
|
clear_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
/* |
|
* SMVP kernels manage VPE enable independently, but uniprocessor |
|
* kernels need to turn it on, even if that wasn't the pre-dvpe() state. |
|
*/ |
|
#ifdef CONFIG_SMP |
|
evpe(vpeflags); |
|
#else |
|
evpe(EVPE_ENABLE); |
|
#endif |
|
emt(dmt_flag); |
|
local_irq_restore(flags); |
|
|
|
list_for_each_entry(notifier, &v->notify, list) |
|
notifier->start(VPE_MODULE_MINOR); |
|
|
|
return 0; |
|
} |
|
|
|
void cleanup_tc(struct tc *tc) |
|
{ |
|
unsigned long flags; |
|
unsigned int mtflags, vpflags; |
|
int tmp; |
|
|
|
local_irq_save(flags); |
|
mtflags = dmt(); |
|
vpflags = dvpe(); |
|
/* Put MVPE's into 'configuration state' */ |
|
set_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
settc(tc->index); |
|
tmp = read_tc_c0_tcstatus(); |
|
|
|
/* mark not allocated and not dynamically allocatable */ |
|
tmp &= ~(TCSTATUS_A | TCSTATUS_DA); |
|
tmp |= TCSTATUS_IXMT; /* interrupt exempt */ |
|
write_tc_c0_tcstatus(tmp); |
|
|
|
write_tc_c0_tchalt(TCHALT_H); |
|
mips_ihb(); |
|
|
|
clear_c0_mvpcontrol(MVPCONTROL_VPC); |
|
evpe(vpflags); |
|
emt(mtflags); |
|
local_irq_restore(flags); |
|
} |
|
|
|
/* module wrapper entry points */ |
|
/* give me a vpe */ |
|
void *vpe_alloc(void) |
|
{ |
|
int i; |
|
struct vpe *v; |
|
|
|
/* find a vpe */ |
|
for (i = 1; i < MAX_VPES; i++) { |
|
v = get_vpe(i); |
|
if (v != NULL) { |
|
v->state = VPE_STATE_INUSE; |
|
return v; |
|
} |
|
} |
|
return NULL; |
|
} |
|
EXPORT_SYMBOL(vpe_alloc); |
|
|
|
/* start running from here */ |
|
int vpe_start(void *vpe, unsigned long start) |
|
{ |
|
struct vpe *v = vpe; |
|
|
|
v->__start = start; |
|
return vpe_run(v); |
|
} |
|
EXPORT_SYMBOL(vpe_start); |
|
|
|
/* halt it for now */ |
|
int vpe_stop(void *vpe) |
|
{ |
|
struct vpe *v = vpe; |
|
struct tc *t; |
|
unsigned int evpe_flags; |
|
|
|
evpe_flags = dvpe(); |
|
|
|
t = list_entry(v->tc.next, struct tc, tc); |
|
if (t != NULL) { |
|
settc(t->index); |
|
write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); |
|
} |
|
|
|
evpe(evpe_flags); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(vpe_stop); |
|
|
|
/* I've done with it thank you */ |
|
int vpe_free(void *vpe) |
|
{ |
|
struct vpe *v = vpe; |
|
struct tc *t; |
|
unsigned int evpe_flags; |
|
|
|
t = list_entry(v->tc.next, struct tc, tc); |
|
if (t == NULL) |
|
return -ENOEXEC; |
|
|
|
evpe_flags = dvpe(); |
|
|
|
/* Put MVPE's into 'configuration state' */ |
|
set_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
settc(t->index); |
|
write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); |
|
|
|
/* halt the TC */ |
|
write_tc_c0_tchalt(TCHALT_H); |
|
mips_ihb(); |
|
|
|
/* mark the TC unallocated */ |
|
write_tc_c0_tcstatus(read_tc_c0_tcstatus() & ~TCSTATUS_A); |
|
|
|
v->state = VPE_STATE_UNUSED; |
|
|
|
clear_c0_mvpcontrol(MVPCONTROL_VPC); |
|
evpe(evpe_flags); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(vpe_free); |
|
|
|
static ssize_t store_kill(struct device *dev, struct device_attribute *attr, |
|
const char *buf, size_t len) |
|
{ |
|
struct vpe *vpe = get_vpe(aprp_cpu_index()); |
|
struct vpe_notifications *notifier; |
|
|
|
list_for_each_entry(notifier, &vpe->notify, list) |
|
notifier->stop(aprp_cpu_index()); |
|
|
|
release_progmem(vpe->load_addr); |
|
cleanup_tc(get_tc(aprp_cpu_index())); |
|
vpe_stop(vpe); |
|
vpe_free(vpe); |
|
|
|
return len; |
|
} |
|
static DEVICE_ATTR(kill, S_IWUSR, NULL, store_kill); |
|
|
|
static ssize_t ntcs_show(struct device *cd, struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
struct vpe *vpe = get_vpe(aprp_cpu_index()); |
|
|
|
return sprintf(buf, "%d\n", vpe->ntcs); |
|
} |
|
|
|
static ssize_t ntcs_store(struct device *dev, struct device_attribute *attr, |
|
const char *buf, size_t len) |
|
{ |
|
struct vpe *vpe = get_vpe(aprp_cpu_index()); |
|
unsigned long new; |
|
int ret; |
|
|
|
ret = kstrtoul(buf, 0, &new); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (new == 0 || new > (hw_tcs - aprp_cpu_index())) |
|
return -EINVAL; |
|
|
|
vpe->ntcs = new; |
|
|
|
return len; |
|
} |
|
static DEVICE_ATTR_RW(ntcs); |
|
|
|
static struct attribute *vpe_attrs[] = { |
|
&dev_attr_kill.attr, |
|
&dev_attr_ntcs.attr, |
|
NULL, |
|
}; |
|
ATTRIBUTE_GROUPS(vpe); |
|
|
|
static void vpe_device_release(struct device *cd) |
|
{ |
|
kfree(cd); |
|
} |
|
|
|
static struct class vpe_class = { |
|
.name = "vpe", |
|
.owner = THIS_MODULE, |
|
.dev_release = vpe_device_release, |
|
.dev_groups = vpe_groups, |
|
}; |
|
|
|
static struct device vpe_device; |
|
|
|
int __init vpe_module_init(void) |
|
{ |
|
unsigned int mtflags, vpflags; |
|
unsigned long flags, val; |
|
struct vpe *v = NULL; |
|
struct tc *t; |
|
int tc, err; |
|
|
|
if (!cpu_has_mipsmt) { |
|
pr_warn("VPE loader: not a MIPS MT capable processor\n"); |
|
return -ENODEV; |
|
} |
|
|
|
if (vpelimit == 0) { |
|
pr_warn("No VPEs reserved for AP/SP, not initialize VPE loader\n" |
|
"Pass maxvpes=<n> argument as kernel argument\n"); |
|
|
|
return -ENODEV; |
|
} |
|
|
|
if (aprp_cpu_index() == 0) { |
|
pr_warn("No TCs reserved for AP/SP, not initialize VPE loader\n" |
|
"Pass maxtcs=<n> argument as kernel argument\n"); |
|
|
|
return -ENODEV; |
|
} |
|
|
|
major = register_chrdev(0, VPE_MODULE_NAME, &vpe_fops); |
|
if (major < 0) { |
|
pr_warn("VPE loader: unable to register character device\n"); |
|
return major; |
|
} |
|
|
|
err = class_register(&vpe_class); |
|
if (err) { |
|
pr_err("vpe_class registration failed\n"); |
|
goto out_chrdev; |
|
} |
|
|
|
device_initialize(&vpe_device); |
|
vpe_device.class = &vpe_class; |
|
vpe_device.parent = NULL; |
|
dev_set_name(&vpe_device, "vpe1"); |
|
vpe_device.devt = MKDEV(major, VPE_MODULE_MINOR); |
|
err = device_add(&vpe_device); |
|
if (err) { |
|
pr_err("Adding vpe_device failed\n"); |
|
goto out_class; |
|
} |
|
|
|
local_irq_save(flags); |
|
mtflags = dmt(); |
|
vpflags = dvpe(); |
|
|
|
/* Put MVPE's into 'configuration state' */ |
|
set_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
val = read_c0_mvpconf0(); |
|
hw_tcs = (val & MVPCONF0_PTC) + 1; |
|
hw_vpes = ((val & MVPCONF0_PVPE) >> MVPCONF0_PVPE_SHIFT) + 1; |
|
|
|
for (tc = aprp_cpu_index(); tc < hw_tcs; tc++) { |
|
/* |
|
* Must re-enable multithreading temporarily or in case we |
|
* reschedule send IPIs or similar we might hang. |
|
*/ |
|
clear_c0_mvpcontrol(MVPCONTROL_VPC); |
|
evpe(vpflags); |
|
emt(mtflags); |
|
local_irq_restore(flags); |
|
t = alloc_tc(tc); |
|
if (!t) { |
|
err = -ENOMEM; |
|
goto out_dev; |
|
} |
|
|
|
local_irq_save(flags); |
|
mtflags = dmt(); |
|
vpflags = dvpe(); |
|
set_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
/* VPE's */ |
|
if (tc < hw_tcs) { |
|
settc(tc); |
|
|
|
v = alloc_vpe(tc); |
|
if (v == NULL) { |
|
pr_warn("VPE: unable to allocate VPE\n"); |
|
goto out_reenable; |
|
} |
|
|
|
v->ntcs = hw_tcs - aprp_cpu_index(); |
|
|
|
/* add the tc to the list of this vpe's tc's. */ |
|
list_add(&t->tc, &v->tc); |
|
|
|
/* deactivate all but vpe0 */ |
|
if (tc >= aprp_cpu_index()) { |
|
unsigned long tmp = read_vpe_c0_vpeconf0(); |
|
|
|
tmp &= ~VPECONF0_VPA; |
|
|
|
/* master VPE */ |
|
tmp |= VPECONF0_MVP; |
|
write_vpe_c0_vpeconf0(tmp); |
|
} |
|
|
|
/* disable multi-threading with TC's */ |
|
write_vpe_c0_vpecontrol(read_vpe_c0_vpecontrol() & |
|
~VPECONTROL_TE); |
|
|
|
if (tc >= vpelimit) { |
|
/* |
|
* Set config to be the same as vpe0, |
|
* particularly kseg0 coherency alg |
|
*/ |
|
write_vpe_c0_config(read_c0_config()); |
|
} |
|
} |
|
|
|
/* TC's */ |
|
t->pvpe = v; /* set the parent vpe */ |
|
|
|
if (tc >= aprp_cpu_index()) { |
|
unsigned long tmp; |
|
|
|
settc(tc); |
|
|
|
/* |
|
* A TC that is bound to any other VPE gets bound to |
|
* VPE0, ideally I'd like to make it homeless but it |
|
* doesn't appear to let me bind a TC to a non-existent |
|
* VPE. Which is perfectly reasonable. |
|
* |
|
* The (un)bound state is visible to an EJTAG probe so |
|
* may notify GDB... |
|
*/ |
|
tmp = read_tc_c0_tcbind(); |
|
if (tmp & TCBIND_CURVPE) { |
|
/* tc is bound >vpe0 */ |
|
write_tc_c0_tcbind(tmp & ~TCBIND_CURVPE); |
|
|
|
t->pvpe = get_vpe(0); /* set the parent vpe */ |
|
} |
|
|
|
/* halt the TC */ |
|
write_tc_c0_tchalt(TCHALT_H); |
|
mips_ihb(); |
|
|
|
tmp = read_tc_c0_tcstatus(); |
|
|
|
/* mark not activated and not dynamically allocatable */ |
|
tmp &= ~(TCSTATUS_A | TCSTATUS_DA); |
|
tmp |= TCSTATUS_IXMT; /* interrupt exempt */ |
|
write_tc_c0_tcstatus(tmp); |
|
} |
|
} |
|
|
|
out_reenable: |
|
/* release config state */ |
|
clear_c0_mvpcontrol(MVPCONTROL_VPC); |
|
|
|
evpe(vpflags); |
|
emt(mtflags); |
|
local_irq_restore(flags); |
|
|
|
return 0; |
|
|
|
out_dev: |
|
device_del(&vpe_device); |
|
|
|
out_class: |
|
class_unregister(&vpe_class); |
|
|
|
out_chrdev: |
|
unregister_chrdev(major, VPE_MODULE_NAME); |
|
|
|
return err; |
|
} |
|
|
|
void __exit vpe_module_exit(void) |
|
{ |
|
struct vpe *v, *n; |
|
|
|
device_del(&vpe_device); |
|
class_unregister(&vpe_class); |
|
unregister_chrdev(major, VPE_MODULE_NAME); |
|
|
|
/* No locking needed here */ |
|
list_for_each_entry_safe(v, n, &vpecontrol.vpe_list, list) { |
|
if (v->state != VPE_STATE_UNUSED) |
|
release_vpe(v); |
|
} |
|
}
|
|
|