Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0
config PPC_CELL
select PPC_64S_HASH_MMU if PPC64
bool
menu "Cell Broadband Engine options"
depends on PPC_CELL
config SPU_FS
tristate "SPU file system"
default m
depends on PPC_CELL
depends on COREDUMP
select SPU_BASE
help
The SPU file system is used to access Synergistic Processing
Units on machines implementing the Broadband Processor
Architecture.
config SPU_BASE
bool
select PPC_COPRO_BASE
endmenu
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SPU_BASE) += spu_callbacks.o spu_base.o \
spu_syscalls.o \
spufs/
@@ -0,0 +1,793 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Low-level SPU handling
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#undef DEBUG
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/linux_logo.h>
#include <linux/syscore_ops.h>
#include <linux/sysfs.h>
#include <asm/spu.h>
#include <asm/spu_priv1.h>
#include <asm/spu_csa.h>
#include <asm/kexec.h>
const struct spu_management_ops *spu_management_ops;
EXPORT_SYMBOL_GPL(spu_management_ops);
const struct spu_priv1_ops *spu_priv1_ops;
EXPORT_SYMBOL_GPL(spu_priv1_ops);
struct cbe_spu_info cbe_spu_info[MAX_NUMNODES];
EXPORT_SYMBOL_GPL(cbe_spu_info);
/*
* The spufs fault-handling code needs to call force_sig_fault to raise signals
* on DMA errors. Export it here to avoid general kernel-wide access to this
* function
*/
EXPORT_SYMBOL_GPL(force_sig_fault);
/*
* Protects cbe_spu_info and spu->number.
*/
static DEFINE_SPINLOCK(spu_lock);
/*
* List of all spus in the system.
*
* This list is iterated by callers from irq context and callers that
* want to sleep. Thus modifications need to be done with both
* spu_full_list_lock and spu_full_list_mutex held, while iterating
* through it requires either of these locks.
*
* In addition spu_full_list_lock protects all assignments to
* spu->mm.
*/
static LIST_HEAD(spu_full_list);
static DEFINE_SPINLOCK(spu_full_list_lock);
static DEFINE_MUTEX(spu_full_list_mutex);
void spu_invalidate_slbs(struct spu *spu)
{
struct spu_priv2 __iomem *priv2 = spu->priv2;
unsigned long flags;
spin_lock_irqsave(&spu->register_lock, flags);
if (spu_mfc_sr1_get(spu) & MFC_STATE1_RELOCATE_MASK)
out_be64(&priv2->slb_invalidate_all_W, 0UL);
spin_unlock_irqrestore(&spu->register_lock, flags);
}
EXPORT_SYMBOL_GPL(spu_invalidate_slbs);
/* This is called by the MM core when a segment size is changed, to
* request a flush of all the SPEs using a given mm
*/
void spu_flush_all_slbs(struct mm_struct *mm)
{
struct spu *spu;
unsigned long flags;
spin_lock_irqsave(&spu_full_list_lock, flags);
list_for_each_entry(spu, &spu_full_list, full_list) {
if (spu->mm == mm)
spu_invalidate_slbs(spu);
}
spin_unlock_irqrestore(&spu_full_list_lock, flags);
}
/* The hack below stinks... try to do something better one of
* these days... Does it even work properly with NR_CPUS == 1 ?
*/
static inline void mm_needs_global_tlbie(struct mm_struct *mm)
{
int nr = (NR_CPUS > 1) ? NR_CPUS : NR_CPUS + 1;
/* Global TLBIE broadcast required with SPEs. */
bitmap_fill(cpumask_bits(mm_cpumask(mm)), nr);
}
void spu_associate_mm(struct spu *spu, struct mm_struct *mm)
{
unsigned long flags;
spin_lock_irqsave(&spu_full_list_lock, flags);
spu->mm = mm;
spin_unlock_irqrestore(&spu_full_list_lock, flags);
if (mm)
mm_needs_global_tlbie(mm);
}
EXPORT_SYMBOL_GPL(spu_associate_mm);
int spu_64k_pages_available(void)
{
return mmu_psize_defs[MMU_PAGE_64K].shift != 0;
}
EXPORT_SYMBOL_GPL(spu_64k_pages_available);
static void spu_restart_dma(struct spu *spu)
{
struct spu_priv2 __iomem *priv2 = spu->priv2;
if (!test_bit(SPU_CONTEXT_SWITCH_PENDING, &spu->flags))
out_be64(&priv2->mfc_control_RW, MFC_CNTL_RESTART_DMA_COMMAND);
else {
set_bit(SPU_CONTEXT_FAULT_PENDING, &spu->flags);
mb();
}
}
static inline void spu_load_slb(struct spu *spu, int slbe, struct copro_slb *slb)
{
struct spu_priv2 __iomem *priv2 = spu->priv2;
pr_debug("%s: adding SLB[%d] 0x%016llx 0x%016llx\n",
__func__, slbe, slb->vsid, slb->esid);
out_be64(&priv2->slb_index_W, slbe);
/* set invalid before writing vsid */
out_be64(&priv2->slb_esid_RW, 0);
/* now it's safe to write the vsid */
out_be64(&priv2->slb_vsid_RW, slb->vsid);
/* setting the new esid makes the entry valid again */
out_be64(&priv2->slb_esid_RW, slb->esid);
}
static int __spu_trap_data_seg(struct spu *spu, unsigned long ea)
{
struct copro_slb slb;
int ret;
ret = copro_calculate_slb(spu->mm, ea, &slb);
if (ret)
return ret;
spu_load_slb(spu, spu->slb_replace, &slb);
spu->slb_replace++;
if (spu->slb_replace >= 8)
spu->slb_replace = 0;
spu_restart_dma(spu);
spu->stats.slb_flt++;
return 0;
}
extern int hash_page(unsigned long ea, unsigned long access,
unsigned long trap, unsigned long dsisr); //XXX
static int __spu_trap_data_map(struct spu *spu, unsigned long ea, u64 dsisr)
{
int ret;
pr_debug("%s, %llx, %lx\n", __func__, dsisr, ea);
/*
* Handle kernel space hash faults immediately. User hash
* faults need to be deferred to process context.
*/
if ((dsisr & MFC_DSISR_PTE_NOT_FOUND) &&
(get_region_id(ea) != USER_REGION_ID)) {
spin_unlock(&spu->register_lock);
ret = hash_page(ea,
_PAGE_PRESENT | _PAGE_READ | _PAGE_PRIVILEGED,
0x300, dsisr);
spin_lock(&spu->register_lock);
if (!ret) {
spu_restart_dma(spu);
return 0;
}
}
spu->class_1_dar = ea;
spu->class_1_dsisr = dsisr;
spu->stop_callback(spu, 1);
spu->class_1_dar = 0;
spu->class_1_dsisr = 0;
return 0;
}
static void __spu_kernel_slb(void *addr, struct copro_slb *slb)
{
unsigned long ea = (unsigned long)addr;
u64 llp;
if (get_region_id(ea) == LINEAR_MAP_REGION_ID)
llp = mmu_psize_defs[mmu_linear_psize].sllp;
else
llp = mmu_psize_defs[mmu_virtual_psize].sllp;
slb->vsid = (get_kernel_vsid(ea, MMU_SEGSIZE_256M) << SLB_VSID_SHIFT) |
SLB_VSID_KERNEL | llp;
slb->esid = (ea & ESID_MASK) | SLB_ESID_V;
}
/**
* Given an array of @nr_slbs SLB entries, @slbs, return non-zero if the
* address @new_addr is present.
*/
static inline int __slb_present(struct copro_slb *slbs, int nr_slbs,
void *new_addr)
{
unsigned long ea = (unsigned long)new_addr;
int i;
for (i = 0; i < nr_slbs; i++)
if (!((slbs[i].esid ^ ea) & ESID_MASK))
return 1;
return 0;
}
/**
* Setup the SPU kernel SLBs, in preparation for a context save/restore. We
* need to map both the context save area, and the save/restore code.
*
* Because the lscsa and code may cross segment boundaries, we check to see
* if mappings are required for the start and end of each range. We currently
* assume that the mappings are smaller that one segment - if not, something
* is seriously wrong.
*/
void spu_setup_kernel_slbs(struct spu *spu, struct spu_lscsa *lscsa,
void *code, int code_size)
{
struct copro_slb slbs[4];
int i, nr_slbs = 0;
/* start and end addresses of both mappings */
void *addrs[] = {
lscsa, (void *)lscsa + sizeof(*lscsa) - 1,
code, code + code_size - 1
};
/* check the set of addresses, and create a new entry in the slbs array
* if there isn't already a SLB for that address */
for (i = 0; i < ARRAY_SIZE(addrs); i++) {
if (__slb_present(slbs, nr_slbs, addrs[i]))
continue;
__spu_kernel_slb(addrs[i], &slbs[nr_slbs]);
nr_slbs++;
}
spin_lock_irq(&spu->register_lock);
/* Add the set of SLBs */
for (i = 0; i < nr_slbs; i++)
spu_load_slb(spu, i, &slbs[i]);
spin_unlock_irq(&spu->register_lock);
}
EXPORT_SYMBOL_GPL(spu_setup_kernel_slbs);
static irqreturn_t
spu_irq_class_0(int irq, void *data)
{
struct spu *spu;
unsigned long stat, mask;
spu = data;
spin_lock(&spu->register_lock);
mask = spu_int_mask_get(spu, 0);
stat = spu_int_stat_get(spu, 0) & mask;
spu->class_0_pending |= stat;
spu->class_0_dar = spu_mfc_dar_get(spu);
spu->stop_callback(spu, 0);
spu->class_0_pending = 0;
spu->class_0_dar = 0;
spu_int_stat_clear(spu, 0, stat);
spin_unlock(&spu->register_lock);
return IRQ_HANDLED;
}
static irqreturn_t
spu_irq_class_1(int irq, void *data)
{
struct spu *spu;
unsigned long stat, mask, dar, dsisr;
spu = data;
/* atomically read & clear class1 status. */
spin_lock(&spu->register_lock);
mask = spu_int_mask_get(spu, 1);
stat = spu_int_stat_get(spu, 1) & mask;
dar = spu_mfc_dar_get(spu);
dsisr = spu_mfc_dsisr_get(spu);
if (stat & CLASS1_STORAGE_FAULT_INTR)
spu_mfc_dsisr_set(spu, 0ul);
spu_int_stat_clear(spu, 1, stat);
pr_debug("%s: %lx %lx %lx %lx\n", __func__, mask, stat,
dar, dsisr);
if (stat & CLASS1_SEGMENT_FAULT_INTR)
__spu_trap_data_seg(spu, dar);
if (stat & CLASS1_STORAGE_FAULT_INTR)
__spu_trap_data_map(spu, dar, dsisr);
spu->class_1_dsisr = 0;
spu->class_1_dar = 0;
spin_unlock(&spu->register_lock);
return stat ? IRQ_HANDLED : IRQ_NONE;
}
static irqreturn_t
spu_irq_class_2(int irq, void *data)
{
struct spu *spu;
unsigned long stat;
unsigned long mask;
const int mailbox_intrs =
CLASS2_MAILBOX_THRESHOLD_INTR | CLASS2_MAILBOX_INTR;
spu = data;
spin_lock(&spu->register_lock);
stat = spu_int_stat_get(spu, 2);
mask = spu_int_mask_get(spu, 2);
/* ignore interrupts we're not waiting for */
stat &= mask;
/* mailbox interrupts are level triggered. mask them now before
* acknowledging */
if (stat & mailbox_intrs)
spu_int_mask_and(spu, 2, ~(stat & mailbox_intrs));
/* acknowledge all interrupts before the callbacks */
spu_int_stat_clear(spu, 2, stat);
pr_debug("class 2 interrupt %d, %lx, %lx\n", irq, stat, mask);
if (stat & CLASS2_MAILBOX_INTR)
spu->ibox_callback(spu);
if (stat & CLASS2_SPU_STOP_INTR)
spu->stop_callback(spu, 2);
if (stat & CLASS2_SPU_HALT_INTR)
spu->stop_callback(spu, 2);
if (stat & CLASS2_SPU_DMA_TAG_GROUP_COMPLETE_INTR)
spu->mfc_callback(spu);
if (stat & CLASS2_MAILBOX_THRESHOLD_INTR)
spu->wbox_callback(spu);
spu->stats.class2_intr++;
spin_unlock(&spu->register_lock);
return stat ? IRQ_HANDLED : IRQ_NONE;
}
static int __init spu_request_irqs(struct spu *spu)
{
int ret = 0;
if (spu->irqs[0]) {
snprintf(spu->irq_c0, sizeof (spu->irq_c0), "spe%02d.0",
spu->number);
ret = request_irq(spu->irqs[0], spu_irq_class_0,
0, spu->irq_c0, spu);
if (ret)
goto bail0;
}
if (spu->irqs[1]) {
snprintf(spu->irq_c1, sizeof (spu->irq_c1), "spe%02d.1",
spu->number);
ret = request_irq(spu->irqs[1], spu_irq_class_1,
0, spu->irq_c1, spu);
if (ret)
goto bail1;
}
if (spu->irqs[2]) {
snprintf(spu->irq_c2, sizeof (spu->irq_c2), "spe%02d.2",
spu->number);
ret = request_irq(spu->irqs[2], spu_irq_class_2,
0, spu->irq_c2, spu);
if (ret)
goto bail2;
}
return 0;
bail2:
if (spu->irqs[1])
free_irq(spu->irqs[1], spu);
bail1:
if (spu->irqs[0])
free_irq(spu->irqs[0], spu);
bail0:
return ret;
}
static void spu_free_irqs(struct spu *spu)
{
if (spu->irqs[0])
free_irq(spu->irqs[0], spu);
if (spu->irqs[1])
free_irq(spu->irqs[1], spu);
if (spu->irqs[2])
free_irq(spu->irqs[2], spu);
}
void spu_init_channels(struct spu *spu)
{
static const struct {
unsigned channel;
unsigned count;
} zero_list[] = {
{ 0x00, 1, }, { 0x01, 1, }, { 0x03, 1, }, { 0x04, 1, },
{ 0x18, 1, }, { 0x19, 1, }, { 0x1b, 1, }, { 0x1d, 1, },
}, count_list[] = {
{ 0x00, 0, }, { 0x03, 0, }, { 0x04, 0, }, { 0x15, 16, },
{ 0x17, 1, }, { 0x18, 0, }, { 0x19, 0, }, { 0x1b, 0, },
{ 0x1c, 1, }, { 0x1d, 0, }, { 0x1e, 1, },
};
struct spu_priv2 __iomem *priv2;
int i;
priv2 = spu->priv2;
/* initialize all channel data to zero */
for (i = 0; i < ARRAY_SIZE(zero_list); i++) {
int count;
out_be64(&priv2->spu_chnlcntptr_RW, zero_list[i].channel);
for (count = 0; count < zero_list[i].count; count++)
out_be64(&priv2->spu_chnldata_RW, 0);
}
/* initialize channel counts to meaningful values */
for (i = 0; i < ARRAY_SIZE(count_list); i++) {
out_be64(&priv2->spu_chnlcntptr_RW, count_list[i].channel);
out_be64(&priv2->spu_chnlcnt_RW, count_list[i].count);
}
}
EXPORT_SYMBOL_GPL(spu_init_channels);
static const struct bus_type spu_subsys = {
.name = "spu",
.dev_name = "spu",
};
int spu_add_dev_attr(struct device_attribute *attr)
{
struct spu *spu;
mutex_lock(&spu_full_list_mutex);
list_for_each_entry(spu, &spu_full_list, full_list)
device_create_file(&spu->dev, attr);
mutex_unlock(&spu_full_list_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(spu_add_dev_attr);
int spu_add_dev_attr_group(const struct attribute_group *attrs)
{
struct spu *spu;
int rc = 0;
mutex_lock(&spu_full_list_mutex);
list_for_each_entry(spu, &spu_full_list, full_list) {
rc = sysfs_create_group(&spu->dev.kobj, attrs);
/* we're in trouble here, but try unwinding anyway */
if (rc) {
printk(KERN_ERR "%s: can't create sysfs group '%s'\n",
__func__, attrs->name);
list_for_each_entry_continue_reverse(spu,
&spu_full_list, full_list)
sysfs_remove_group(&spu->dev.kobj, attrs);
break;
}
}
mutex_unlock(&spu_full_list_mutex);
return rc;
}
EXPORT_SYMBOL_GPL(spu_add_dev_attr_group);
void spu_remove_dev_attr(struct device_attribute *attr)
{
struct spu *spu;
mutex_lock(&spu_full_list_mutex);
list_for_each_entry(spu, &spu_full_list, full_list)
device_remove_file(&spu->dev, attr);
mutex_unlock(&spu_full_list_mutex);
}
EXPORT_SYMBOL_GPL(spu_remove_dev_attr);
void spu_remove_dev_attr_group(const struct attribute_group *attrs)
{
struct spu *spu;
mutex_lock(&spu_full_list_mutex);
list_for_each_entry(spu, &spu_full_list, full_list)
sysfs_remove_group(&spu->dev.kobj, attrs);
mutex_unlock(&spu_full_list_mutex);
}
EXPORT_SYMBOL_GPL(spu_remove_dev_attr_group);
static int __init spu_create_dev(struct spu *spu)
{
int ret;
spu->dev.id = spu->number;
spu->dev.bus = &spu_subsys;
ret = device_register(&spu->dev);
if (ret) {
printk(KERN_ERR "Can't register SPU %d with sysfs\n",
spu->number);
return ret;
}
sysfs_add_device_to_node(&spu->dev, spu->node);
return 0;
}
static int __init create_spu(void *data)
{
struct spu *spu;
int ret;
static int number;
unsigned long flags;
ret = -ENOMEM;
spu = kzalloc_obj(*spu);
if (!spu)
goto out;
spu->alloc_state = SPU_FREE;
spin_lock_init(&spu->register_lock);
spin_lock(&spu_lock);
spu->number = number++;
spin_unlock(&spu_lock);
ret = spu_create_spu(spu, data);
if (ret)
goto out_free;
spu_mfc_sdr_setup(spu);
spu_mfc_sr1_set(spu, 0x33);
ret = spu_request_irqs(spu);
if (ret)
goto out_destroy;
ret = spu_create_dev(spu);
if (ret)
goto out_free_irqs;
mutex_lock(&cbe_spu_info[spu->node].list_mutex);
list_add(&spu->cbe_list, &cbe_spu_info[spu->node].spus);
cbe_spu_info[spu->node].n_spus++;
mutex_unlock(&cbe_spu_info[spu->node].list_mutex);
mutex_lock(&spu_full_list_mutex);
spin_lock_irqsave(&spu_full_list_lock, flags);
list_add(&spu->full_list, &spu_full_list);
spin_unlock_irqrestore(&spu_full_list_lock, flags);
mutex_unlock(&spu_full_list_mutex);
spu->stats.util_state = SPU_UTIL_IDLE_LOADED;
spu->stats.tstamp = ktime_get_ns();
INIT_LIST_HEAD(&spu->aff_list);
goto out;
out_free_irqs:
spu_free_irqs(spu);
out_destroy:
spu_destroy_spu(spu);
out_free:
kfree(spu);
out:
return ret;
}
static const char *spu_state_names[] = {
"user", "system", "iowait", "idle"
};
static unsigned long long spu_acct_time(struct spu *spu,
enum spu_utilization_state state)
{
unsigned long long time = spu->stats.times[state];
/*
* If the spu is idle or the context is stopped, utilization
* statistics are not updated. Apply the time delta from the
* last recorded state of the spu.
*/
if (spu->stats.util_state == state)
time += ktime_get_ns() - spu->stats.tstamp;
return time / NSEC_PER_MSEC;
}
static ssize_t spu_stat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spu *spu = container_of(dev, struct spu, dev);
return sysfs_emit(buf,
"%s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\n",
spu_state_names[spu->stats.util_state],
spu_acct_time(spu, SPU_UTIL_USER),
spu_acct_time(spu, SPU_UTIL_SYSTEM),
spu_acct_time(spu, SPU_UTIL_IOWAIT),
spu_acct_time(spu, SPU_UTIL_IDLE_LOADED),
spu->stats.vol_ctx_switch,
spu->stats.invol_ctx_switch,
spu->stats.slb_flt,
spu->stats.hash_flt,
spu->stats.min_flt,
spu->stats.maj_flt,
spu->stats.class2_intr,
spu->stats.libassist);
}
static DEVICE_ATTR(stat, 0444, spu_stat_show, NULL);
#ifdef CONFIG_KEXEC_CORE
struct crash_spu_info {
struct spu *spu;
u32 saved_spu_runcntl_RW;
u32 saved_spu_status_R;
u32 saved_spu_npc_RW;
u64 saved_mfc_sr1_RW;
u64 saved_mfc_dar;
u64 saved_mfc_dsisr;
};
#define CRASH_NUM_SPUS 16 /* Enough for current hardware */
static struct crash_spu_info crash_spu_info[CRASH_NUM_SPUS];
static void crash_kexec_stop_spus(void)
{
struct spu *spu;
int i;
u64 tmp;
for (i = 0; i < CRASH_NUM_SPUS; i++) {
if (!crash_spu_info[i].spu)
continue;
spu = crash_spu_info[i].spu;
crash_spu_info[i].saved_spu_runcntl_RW =
in_be32(&spu->problem->spu_runcntl_RW);
crash_spu_info[i].saved_spu_status_R =
in_be32(&spu->problem->spu_status_R);
crash_spu_info[i].saved_spu_npc_RW =
in_be32(&spu->problem->spu_npc_RW);
crash_spu_info[i].saved_mfc_dar = spu_mfc_dar_get(spu);
crash_spu_info[i].saved_mfc_dsisr = spu_mfc_dsisr_get(spu);
tmp = spu_mfc_sr1_get(spu);
crash_spu_info[i].saved_mfc_sr1_RW = tmp;
tmp &= ~MFC_STATE1_MASTER_RUN_CONTROL_MASK;
spu_mfc_sr1_set(spu, tmp);
__delay(200);
}
}
static void __init crash_register_spus(struct list_head *list)
{
struct spu *spu;
int ret;
list_for_each_entry(spu, list, full_list) {
if (WARN_ON(spu->number >= CRASH_NUM_SPUS))
continue;
crash_spu_info[spu->number].spu = spu;
}
ret = crash_shutdown_register(&crash_kexec_stop_spus);
if (ret)
printk(KERN_ERR "Could not register SPU crash handler");
}
#else
static inline void crash_register_spus(struct list_head *list)
{
}
#endif
static void spu_shutdown(void *data)
{
struct spu *spu;
mutex_lock(&spu_full_list_mutex);
list_for_each_entry(spu, &spu_full_list, full_list) {
spu_free_irqs(spu);
spu_destroy_spu(spu);
}
mutex_unlock(&spu_full_list_mutex);
}
static const struct syscore_ops spu_syscore_ops = {
.shutdown = spu_shutdown,
};
static struct syscore spu_syscore = {
.ops = &spu_syscore_ops,
};
static int __init init_spu_base(void)
{
int i, ret = 0;
for (i = 0; i < MAX_NUMNODES; i++) {
mutex_init(&cbe_spu_info[i].list_mutex);
INIT_LIST_HEAD(&cbe_spu_info[i].spus);
}
if (!spu_management_ops)
goto out;
/* create system subsystem for spus */
ret = subsys_system_register(&spu_subsys, NULL);
if (ret)
goto out;
ret = spu_enumerate_spus(create_spu);
if (ret < 0) {
printk(KERN_WARNING "%s: Error initializing spus\n",
__func__);
goto out_unregister_subsys;
}
if (ret > 0)
fb_append_extra_logo(&logo_spe_clut224, ret);
mutex_lock(&spu_full_list_mutex);
crash_register_spus(&spu_full_list);
mutex_unlock(&spu_full_list_mutex);
spu_add_dev_attr(&dev_attr_stat);
register_syscore(&spu_syscore);
spu_init_affinity();
return 0;
out_unregister_subsys:
bus_unregister(&spu_subsys);
out:
return ret;
}
device_initcall(init_spu_base);
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* System call callback functions for SPUs
*/
#undef DEBUG
#include <linux/kallsyms.h>
#include <linux/export.h>
#include <linux/syscalls.h>
#include <asm/spu.h>
#include <asm/syscalls.h>
#include <asm/unistd.h>
/*
* This table defines the system calls that an SPU can call.
* It is currently a subset of the 64 bit powerpc system calls,
* with the exact semantics.
*
* The reasons for disabling some of the system calls are:
* 1. They interact with the way SPU syscalls are handled
* and we can't let them execute ever:
* restart_syscall, exit, for, execve, ptrace, ...
* 2. They are deprecated and replaced by other means:
* uselib, pciconfig_*, sysfs, ...
* 3. They are somewhat interacting with the system in a way
* we don't want an SPU to:
* reboot, init_module, mount, kexec_load
* 4. They are optional and we can't rely on them being
* linked into the kernel. Unfortunately, the cond_syscall
* helper does not work here as it does not add the necessary
* opd symbols:
* mbind, mq_open, ipc, ...
*/
static const syscall_fn spu_syscall_table[] = {
#define __SYSCALL_WITH_COMPAT(nr, entry, compat) __SYSCALL(nr, entry)
#define __SYSCALL(nr, entry) [nr] = (void *) entry,
#include <asm/syscall_table_spu.h>
};
long spu_sys_callback(struct spu_syscall_block *s)
{
syscall_fn syscall;
if (s->nr_ret >= ARRAY_SIZE(spu_syscall_table)) {
pr_debug("%s: invalid syscall #%lld", __func__, s->nr_ret);
return -ENOSYS;
}
syscall = spu_syscall_table[s->nr_ret];
pr_debug("SPU-syscall "
"%pSR:syscall%lld(%llx, %llx, %llx, %llx, %llx, %llx)\n",
syscall,
s->nr_ret,
s->parm[0], s->parm[1], s->parm[2],
s->parm[3], s->parm[4], s->parm[5]);
return syscall(s->parm[0], s->parm[1], s->parm[2],
s->parm[3], s->parm[4], s->parm[5]);
}
EXPORT_SYMBOL_GPL(spu_sys_callback);
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU file system -- system call stubs
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
* (C) Copyright 2006-2007, IBM Corporation
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/rcupdate.h>
#include <linux/binfmts.h>
#include <asm/spu.h>
/* protected by rcu */
static struct spufs_calls *spufs_calls;
#ifdef CONFIG_SPU_FS_MODULE
static inline struct spufs_calls *spufs_calls_get(void)
{
struct spufs_calls *calls = NULL;
rcu_read_lock();
calls = rcu_dereference(spufs_calls);
if (calls && !try_module_get(calls->owner))
calls = NULL;
rcu_read_unlock();
return calls;
}
static inline void spufs_calls_put(struct spufs_calls *calls)
{
if (!calls)
return;
BUG_ON(calls != spufs_calls);
/* we don't need to rcu this, as we hold a reference to the module */
module_put(spufs_calls->owner);
}
#else /* !defined CONFIG_SPU_FS_MODULE */
static inline struct spufs_calls *spufs_calls_get(void)
{
return spufs_calls;
}
static inline void spufs_calls_put(struct spufs_calls *calls) { }
#endif /* CONFIG_SPU_FS_MODULE */
DEFINE_CLASS(spufs_calls, struct spufs_calls *, spufs_calls_put(_T), spufs_calls_get(), void)
SYSCALL_DEFINE4(spu_create, const char __user *, name, unsigned int, flags,
umode_t, mode, int, neighbor_fd)
{
CLASS(spufs_calls, calls)();
if (!calls)
return -ENOSYS;
if (flags & SPU_CREATE_AFFINITY_SPU) {
CLASS(fd, neighbor)(neighbor_fd);
if (fd_empty(neighbor))
return -EBADF;
return calls->create_thread(name, flags, mode, fd_file(neighbor));
} else {
return calls->create_thread(name, flags, mode, NULL);
}
}
SYSCALL_DEFINE3(spu_run,int, fd, __u32 __user *, unpc, __u32 __user *, ustatus)
{
CLASS(spufs_calls, calls)();
if (!calls)
return -ENOSYS;
CLASS(fd, arg)(fd);
if (fd_empty(arg))
return -EBADF;
return calls->spu_run(fd_file(arg), unpc, ustatus);
}
#ifdef CONFIG_COREDUMP
int elf_coredump_extra_notes_size(void)
{
CLASS(spufs_calls, calls)();
if (!calls)
return 0;
return calls->coredump_extra_notes_size();
}
int elf_coredump_extra_notes_write(struct coredump_params *cprm)
{
CLASS(spufs_calls, calls)();
if (!calls)
return 0;
return calls->coredump_extra_notes_write(cprm);
}
#endif
void notify_spus_active(void)
{
struct spufs_calls *calls;
calls = spufs_calls_get();
if (!calls)
return;
calls->notify_spus_active();
spufs_calls_put(calls);
return;
}
int register_spu_syscalls(struct spufs_calls *calls)
{
if (spufs_calls)
return -EBUSY;
rcu_assign_pointer(spufs_calls, calls);
return 0;
}
EXPORT_SYMBOL_GPL(register_spu_syscalls);
void unregister_spu_syscalls(struct spufs_calls *calls)
{
BUG_ON(spufs_calls->owner != calls->owner);
RCU_INIT_POINTER(spufs_calls, NULL);
synchronize_rcu();
}
EXPORT_SYMBOL_GPL(unregister_spu_syscalls);
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
spu_save_dump.h
spu_restore_dump.h
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SPU_FS) += spufs.o
spufs-y += inode.o file.o context.o syscalls.o
spufs-y += sched.o backing_ops.o hw_ops.o run.o gang.o
spufs-y += switch.o fault.o lscsa_alloc.o
spufs-$(CONFIG_COREDUMP) += coredump.o
# magic for the trace events
CFLAGS_sched.o := -I$(src)
# Rules to build switch.o with the help of SPU tool chain
SPU_CROSS := spu-
SPU_CC := $(SPU_CROSS)gcc
SPU_AS := $(SPU_CROSS)gcc
SPU_LD := $(SPU_CROSS)ld
SPU_OBJCOPY := $(SPU_CROSS)objcopy
SPU_CFLAGS := -O2 -Wall -I$(srctree)/include -D__KERNEL__
SPU_AFLAGS := -c -D__ASSEMBLY__ -I$(srctree)/include -D__KERNEL__
SPU_LDFLAGS := -N -Ttext=0x0
$(obj)/switch.o: $(obj)/spu_save_dump.h $(obj)/spu_restore_dump.h
clean-files := spu_save_dump.h spu_restore_dump.h
# Compile SPU files
cmd_spu_cc = $(SPU_CC) $(SPU_CFLAGS) -c -o $@ $<
quiet_cmd_spu_cc = SPU_CC $@
$(obj)/spu_%.o: $(src)/spu_%.c
$(call if_changed,spu_cc)
# Assemble SPU files
cmd_spu_as = $(SPU_AS) $(SPU_AFLAGS) -o $@ $<
quiet_cmd_spu_as = SPU_AS $@
$(obj)/spu_%.o: $(src)/spu_%.S
$(call if_changed,spu_as)
# Link SPU Executables
cmd_spu_ld = $(SPU_LD) $(SPU_LDFLAGS) -o $@ $^
quiet_cmd_spu_ld = SPU_LD $@
$(obj)/spu_%: $(obj)/spu_%_crt0.o $(obj)/spu_%.o
$(call if_changed,spu_ld)
# Copy into binary format
cmd_spu_objcopy = $(SPU_OBJCOPY) -O binary $< $@
quiet_cmd_spu_objcopy = OBJCOPY $@
$(obj)/spu_%.bin: $(src)/spu_%
$(call if_changed,spu_objcopy)
# create C code from ELF executable
cmd_hexdump = ( \
echo "/*" ; \
echo " * $*_dump.h: Copyright (C) 2005 IBM." ; \
echo " * Hex-dump auto generated from $*.c." ; \
echo " * Do not edit!" ; \
echo " */" ; \
echo "static unsigned int $*_code[] " \
"__attribute__((__aligned__(128))) = {" ; \
hexdump -v -e '"0x" 4/1 "%02x" "," "\n"' $< ; \
echo "};" ; \
) > $@
quiet_cmd_hexdump = HEXDUMP $@
$(obj)/%_dump.h: $(obj)/%.bin
$(call if_changed,hexdump)
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* backing_ops.c - query/set operations on saved SPU context.
*
* Copyright (C) IBM 2005
* Author: Mark Nutter <mnutter@us.ibm.com>
*
* These register operations allow SPUFS to operate on saved
* SPU contexts rather than hardware.
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/smp.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/poll.h>
#include <asm/io.h>
#include <asm/spu.h>
#include <asm/spu_csa.h>
#include <asm/spu_info.h>
#include <asm/mmu_context.h>
#include "spufs.h"
/*
* Reads/writes to various problem and priv2 registers require
* state changes, i.e. generate SPU events, modify channel
* counts, etc.
*/
static void gen_spu_event(struct spu_context *ctx, u32 event)
{
u64 ch0_cnt;
u64 ch0_data;
u64 ch1_data;
ch0_cnt = ctx->csa.spu_chnlcnt_RW[0];
ch0_data = ctx->csa.spu_chnldata_RW[0];
ch1_data = ctx->csa.spu_chnldata_RW[1];
ctx->csa.spu_chnldata_RW[0] |= event;
if ((ch0_cnt == 0) && !(ch0_data & event) && (ch1_data & event)) {
ctx->csa.spu_chnlcnt_RW[0] = 1;
}
}
static int spu_backing_mbox_read(struct spu_context *ctx, u32 * data)
{
u32 mbox_stat;
int ret = 0;
spin_lock(&ctx->csa.register_lock);
mbox_stat = ctx->csa.prob.mb_stat_R;
if (mbox_stat & 0x0000ff) {
/* Read the first available word.
* Implementation note: the depth
* of pu_mb_R is currently 1.
*/
*data = ctx->csa.prob.pu_mb_R;
ctx->csa.prob.mb_stat_R &= ~(0x0000ff);
ctx->csa.spu_chnlcnt_RW[28] = 1;
gen_spu_event(ctx, MFC_PU_MAILBOX_AVAILABLE_EVENT);
ret = 4;
}
spin_unlock(&ctx->csa.register_lock);
return ret;
}
static u32 spu_backing_mbox_stat_read(struct spu_context *ctx)
{
return ctx->csa.prob.mb_stat_R;
}
static __poll_t spu_backing_mbox_stat_poll(struct spu_context *ctx,
__poll_t events)
{
__poll_t ret;
u32 stat;
ret = 0;
spin_lock_irq(&ctx->csa.register_lock);
stat = ctx->csa.prob.mb_stat_R;
/* if the requested event is there, return the poll
mask, otherwise enable the interrupt to get notified,
but first mark any pending interrupts as done so
we don't get woken up unnecessarily */
if (events & (EPOLLIN | EPOLLRDNORM)) {
if (stat & 0xff0000)
ret |= EPOLLIN | EPOLLRDNORM;
else {
ctx->csa.priv1.int_stat_class2_RW &=
~CLASS2_MAILBOX_INTR;
ctx->csa.priv1.int_mask_class2_RW |=
CLASS2_ENABLE_MAILBOX_INTR;
}
}
if (events & (EPOLLOUT | EPOLLWRNORM)) {
if (stat & 0x00ff00)
ret = EPOLLOUT | EPOLLWRNORM;
else {
ctx->csa.priv1.int_stat_class2_RW &=
~CLASS2_MAILBOX_THRESHOLD_INTR;
ctx->csa.priv1.int_mask_class2_RW |=
CLASS2_ENABLE_MAILBOX_THRESHOLD_INTR;
}
}
spin_unlock_irq(&ctx->csa.register_lock);
return ret;
}
static int spu_backing_ibox_read(struct spu_context *ctx, u32 * data)
{
int ret;
spin_lock(&ctx->csa.register_lock);
if (ctx->csa.prob.mb_stat_R & 0xff0000) {
/* Read the first available word.
* Implementation note: the depth
* of puint_mb_R is currently 1.
*/
*data = ctx->csa.priv2.puint_mb_R;
ctx->csa.prob.mb_stat_R &= ~(0xff0000);
ctx->csa.spu_chnlcnt_RW[30] = 1;
gen_spu_event(ctx, MFC_PU_INT_MAILBOX_AVAILABLE_EVENT);
ret = 4;
} else {
/* make sure we get woken up by the interrupt */
ctx->csa.priv1.int_mask_class2_RW |= CLASS2_ENABLE_MAILBOX_INTR;
ret = 0;
}
spin_unlock(&ctx->csa.register_lock);
return ret;
}
static int spu_backing_wbox_write(struct spu_context *ctx, u32 data)
{
int ret;
spin_lock(&ctx->csa.register_lock);
if ((ctx->csa.prob.mb_stat_R) & 0x00ff00) {
int slot = ctx->csa.spu_chnlcnt_RW[29];
int avail = (ctx->csa.prob.mb_stat_R & 0x00ff00) >> 8;
/* We have space to write wbox_data.
* Implementation note: the depth
* of spu_mb_W is currently 4.
*/
BUG_ON(avail != (4 - slot));
ctx->csa.spu_mailbox_data[slot] = data;
ctx->csa.spu_chnlcnt_RW[29] = ++slot;
ctx->csa.prob.mb_stat_R &= ~(0x00ff00);
ctx->csa.prob.mb_stat_R |= (((4 - slot) & 0xff) << 8);
gen_spu_event(ctx, MFC_SPU_MAILBOX_WRITTEN_EVENT);
ret = 4;
} else {
/* make sure we get woken up by the interrupt when space
becomes available */
ctx->csa.priv1.int_mask_class2_RW |=
CLASS2_ENABLE_MAILBOX_THRESHOLD_INTR;
ret = 0;
}
spin_unlock(&ctx->csa.register_lock);
return ret;
}
static u32 spu_backing_signal1_read(struct spu_context *ctx)
{
return ctx->csa.spu_chnldata_RW[3];
}
static void spu_backing_signal1_write(struct spu_context *ctx, u32 data)
{
spin_lock(&ctx->csa.register_lock);
if (ctx->csa.priv2.spu_cfg_RW & 0x1)
ctx->csa.spu_chnldata_RW[3] |= data;
else
ctx->csa.spu_chnldata_RW[3] = data;
ctx->csa.spu_chnlcnt_RW[3] = 1;
gen_spu_event(ctx, MFC_SIGNAL_1_EVENT);
spin_unlock(&ctx->csa.register_lock);
}
static u32 spu_backing_signal2_read(struct spu_context *ctx)
{
return ctx->csa.spu_chnldata_RW[4];
}
static void spu_backing_signal2_write(struct spu_context *ctx, u32 data)
{
spin_lock(&ctx->csa.register_lock);
if (ctx->csa.priv2.spu_cfg_RW & 0x2)
ctx->csa.spu_chnldata_RW[4] |= data;
else
ctx->csa.spu_chnldata_RW[4] = data;
ctx->csa.spu_chnlcnt_RW[4] = 1;
gen_spu_event(ctx, MFC_SIGNAL_2_EVENT);
spin_unlock(&ctx->csa.register_lock);
}
static void spu_backing_signal1_type_set(struct spu_context *ctx, u64 val)
{
u64 tmp;
spin_lock(&ctx->csa.register_lock);
tmp = ctx->csa.priv2.spu_cfg_RW;
if (val)
tmp |= 1;
else
tmp &= ~1;
ctx->csa.priv2.spu_cfg_RW = tmp;
spin_unlock(&ctx->csa.register_lock);
}
static u64 spu_backing_signal1_type_get(struct spu_context *ctx)
{
return ((ctx->csa.priv2.spu_cfg_RW & 1) != 0);
}
static void spu_backing_signal2_type_set(struct spu_context *ctx, u64 val)
{
u64 tmp;
spin_lock(&ctx->csa.register_lock);
tmp = ctx->csa.priv2.spu_cfg_RW;
if (val)
tmp |= 2;
else
tmp &= ~2;
ctx->csa.priv2.spu_cfg_RW = tmp;
spin_unlock(&ctx->csa.register_lock);
}
static u64 spu_backing_signal2_type_get(struct spu_context *ctx)
{
return ((ctx->csa.priv2.spu_cfg_RW & 2) != 0);
}
static u32 spu_backing_npc_read(struct spu_context *ctx)
{
return ctx->csa.prob.spu_npc_RW;
}
static void spu_backing_npc_write(struct spu_context *ctx, u32 val)
{
ctx->csa.prob.spu_npc_RW = val;
}
static u32 spu_backing_status_read(struct spu_context *ctx)
{
return ctx->csa.prob.spu_status_R;
}
static char *spu_backing_get_ls(struct spu_context *ctx)
{
return ctx->csa.lscsa->ls;
}
static void spu_backing_privcntl_write(struct spu_context *ctx, u64 val)
{
ctx->csa.priv2.spu_privcntl_RW = val;
}
static u32 spu_backing_runcntl_read(struct spu_context *ctx)
{
return ctx->csa.prob.spu_runcntl_RW;
}
static void spu_backing_runcntl_write(struct spu_context *ctx, u32 val)
{
spin_lock(&ctx->csa.register_lock);
ctx->csa.prob.spu_runcntl_RW = val;
if (val & SPU_RUNCNTL_RUNNABLE) {
ctx->csa.prob.spu_status_R &=
~SPU_STATUS_STOPPED_BY_STOP &
~SPU_STATUS_STOPPED_BY_HALT &
~SPU_STATUS_SINGLE_STEP &
~SPU_STATUS_INVALID_INSTR &
~SPU_STATUS_INVALID_CH;
ctx->csa.prob.spu_status_R |= SPU_STATUS_RUNNING;
} else {
ctx->csa.prob.spu_status_R &= ~SPU_STATUS_RUNNING;
}
spin_unlock(&ctx->csa.register_lock);
}
static void spu_backing_runcntl_stop(struct spu_context *ctx)
{
spu_backing_runcntl_write(ctx, SPU_RUNCNTL_STOP);
}
static void spu_backing_master_start(struct spu_context *ctx)
{
struct spu_state *csa = &ctx->csa;
u64 sr1;
spin_lock(&csa->register_lock);
sr1 = csa->priv1.mfc_sr1_RW | MFC_STATE1_MASTER_RUN_CONTROL_MASK;
csa->priv1.mfc_sr1_RW = sr1;
spin_unlock(&csa->register_lock);
}
static void spu_backing_master_stop(struct spu_context *ctx)
{
struct spu_state *csa = &ctx->csa;
u64 sr1;
spin_lock(&csa->register_lock);
sr1 = csa->priv1.mfc_sr1_RW & ~MFC_STATE1_MASTER_RUN_CONTROL_MASK;
csa->priv1.mfc_sr1_RW = sr1;
spin_unlock(&csa->register_lock);
}
static int spu_backing_set_mfc_query(struct spu_context * ctx, u32 mask,
u32 mode)
{
struct spu_problem_collapsed *prob = &ctx->csa.prob;
int ret;
spin_lock(&ctx->csa.register_lock);
ret = -EAGAIN;
if (prob->dma_querytype_RW)
goto out;
ret = 0;
/* FIXME: what are the side-effects of this? */
prob->dma_querymask_RW = mask;
prob->dma_querytype_RW = mode;
/* In the current implementation, the SPU context is always
* acquired in runnable state when new bits are added to the
* mask (tagwait), so it's sufficient just to mask
* dma_tagstatus_R with the 'mask' parameter here.
*/
ctx->csa.prob.dma_tagstatus_R &= mask;
out:
spin_unlock(&ctx->csa.register_lock);
return ret;
}
static u32 spu_backing_read_mfc_tagstatus(struct spu_context * ctx)
{
return ctx->csa.prob.dma_tagstatus_R;
}
static u32 spu_backing_get_mfc_free_elements(struct spu_context *ctx)
{
return ctx->csa.prob.dma_qstatus_R;
}
static int spu_backing_send_mfc_command(struct spu_context *ctx,
struct mfc_dma_command *cmd)
{
int ret;
spin_lock(&ctx->csa.register_lock);
ret = -EAGAIN;
/* FIXME: set up priv2->puq */
spin_unlock(&ctx->csa.register_lock);
return ret;
}
static void spu_backing_restart_dma(struct spu_context *ctx)
{
ctx->csa.priv2.mfc_control_RW |= MFC_CNTL_RESTART_DMA_COMMAND;
}
struct spu_context_ops spu_backing_ops = {
.mbox_read = spu_backing_mbox_read,
.mbox_stat_read = spu_backing_mbox_stat_read,
.mbox_stat_poll = spu_backing_mbox_stat_poll,
.ibox_read = spu_backing_ibox_read,
.wbox_write = spu_backing_wbox_write,
.signal1_read = spu_backing_signal1_read,
.signal1_write = spu_backing_signal1_write,
.signal2_read = spu_backing_signal2_read,
.signal2_write = spu_backing_signal2_write,
.signal1_type_set = spu_backing_signal1_type_set,
.signal1_type_get = spu_backing_signal1_type_get,
.signal2_type_set = spu_backing_signal2_type_set,
.signal2_type_get = spu_backing_signal2_type_get,
.npc_read = spu_backing_npc_read,
.npc_write = spu_backing_npc_write,
.status_read = spu_backing_status_read,
.get_ls = spu_backing_get_ls,
.privcntl_write = spu_backing_privcntl_write,
.runcntl_read = spu_backing_runcntl_read,
.runcntl_write = spu_backing_runcntl_write,
.runcntl_stop = spu_backing_runcntl_stop,
.master_start = spu_backing_master_start,
.master_stop = spu_backing_master_stop,
.set_mfc_query = spu_backing_set_mfc_query,
.read_mfc_tagstatus = spu_backing_read_mfc_tagstatus,
.get_mfc_free_elements = spu_backing_get_mfc_free_elements,
.send_mfc_command = spu_backing_send_mfc_command,
.restart_dma = spu_backing_restart_dma,
};
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU file system -- SPU context management
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/atomic.h>
#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <asm/spu.h>
#include <asm/spu_csa.h>
#include "spufs.h"
#include "sputrace.h"
atomic_t nr_spu_contexts = ATOMIC_INIT(0);
struct spu_context *alloc_spu_context(struct spu_gang *gang)
{
struct spu_context *ctx;
ctx = kzalloc_obj(*ctx);
if (!ctx)
goto out;
/* Binding to physical processor deferred
* until spu_activate().
*/
if (spu_init_csa(&ctx->csa))
goto out_free;
spin_lock_init(&ctx->mmio_lock);
mutex_init(&ctx->mapping_lock);
kref_init(&ctx->kref);
mutex_init(&ctx->state_mutex);
mutex_init(&ctx->run_mutex);
init_waitqueue_head(&ctx->ibox_wq);
init_waitqueue_head(&ctx->wbox_wq);
init_waitqueue_head(&ctx->stop_wq);
init_waitqueue_head(&ctx->mfc_wq);
init_waitqueue_head(&ctx->run_wq);
ctx->state = SPU_STATE_SAVED;
ctx->ops = &spu_backing_ops;
ctx->owner = get_task_mm(current);
INIT_LIST_HEAD(&ctx->rq);
INIT_LIST_HEAD(&ctx->aff_list);
if (gang)
spu_gang_add_ctx(gang, ctx);
__spu_update_sched_info(ctx);
spu_set_timeslice(ctx);
ctx->stats.util_state = SPU_UTIL_IDLE_LOADED;
ctx->stats.tstamp = ktime_get_ns();
atomic_inc(&nr_spu_contexts);
goto out;
out_free:
kfree(ctx);
ctx = NULL;
out:
return ctx;
}
void destroy_spu_context(struct kref *kref)
{
struct spu_context *ctx;
ctx = container_of(kref, struct spu_context, kref);
spu_context_nospu_trace(destroy_spu_context__enter, ctx);
mutex_lock(&ctx->state_mutex);
spu_deactivate(ctx);
mutex_unlock(&ctx->state_mutex);
spu_fini_csa(&ctx->csa);
if (ctx->gang)
spu_gang_remove_ctx(ctx->gang, ctx);
if (ctx->prof_priv_kref)
kref_put(ctx->prof_priv_kref, ctx->prof_priv_release);
BUG_ON(!list_empty(&ctx->rq));
atomic_dec(&nr_spu_contexts);
kfree(ctx->switch_log);
kfree(ctx);
}
struct spu_context * get_spu_context(struct spu_context *ctx)
{
kref_get(&ctx->kref);
return ctx;
}
int put_spu_context(struct spu_context *ctx)
{
return kref_put(&ctx->kref, &destroy_spu_context);
}
/* give up the mm reference when the context is about to be destroyed */
void spu_forget(struct spu_context *ctx)
{
struct mm_struct *mm;
/*
* This is basically an open-coded spu_acquire_saved, except that
* we don't acquire the state mutex interruptible, and we don't
* want this context to be rescheduled on release.
*/
mutex_lock(&ctx->state_mutex);
if (ctx->state != SPU_STATE_SAVED)
spu_deactivate(ctx);
mm = ctx->owner;
ctx->owner = NULL;
mmput(mm);
spu_release(ctx);
}
void spu_unmap_mappings(struct spu_context *ctx)
{
mutex_lock(&ctx->mapping_lock);
if (ctx->local_store)
unmap_mapping_range(ctx->local_store, 0, LS_SIZE, 1);
if (ctx->mfc)
unmap_mapping_range(ctx->mfc, 0, SPUFS_MFC_MAP_SIZE, 1);
if (ctx->cntl)
unmap_mapping_range(ctx->cntl, 0, SPUFS_CNTL_MAP_SIZE, 1);
if (ctx->signal1)
unmap_mapping_range(ctx->signal1, 0, SPUFS_SIGNAL_MAP_SIZE, 1);
if (ctx->signal2)
unmap_mapping_range(ctx->signal2, 0, SPUFS_SIGNAL_MAP_SIZE, 1);
if (ctx->mss)
unmap_mapping_range(ctx->mss, 0, SPUFS_MSS_MAP_SIZE, 1);
if (ctx->psmap)
unmap_mapping_range(ctx->psmap, 0, SPUFS_PS_MAP_SIZE, 1);
mutex_unlock(&ctx->mapping_lock);
}
/**
* spu_acquire_saved - lock spu contex and make sure it is in saved state
* @ctx: spu contex to lock
*/
int spu_acquire_saved(struct spu_context *ctx)
{
int ret;
spu_context_nospu_trace(spu_acquire_saved__enter, ctx);
ret = spu_acquire(ctx);
if (ret)
return ret;
if (ctx->state != SPU_STATE_SAVED) {
set_bit(SPU_SCHED_WAS_ACTIVE, &ctx->sched_flags);
spu_deactivate(ctx);
}
return 0;
}
/**
* spu_release_saved - unlock spu context and return it to the runqueue
* @ctx: context to unlock
*/
void spu_release_saved(struct spu_context *ctx)
{
BUG_ON(ctx->state != SPU_STATE_SAVED);
if (test_and_clear_bit(SPU_SCHED_WAS_ACTIVE, &ctx->sched_flags) &&
test_bit(SPU_SCHED_SPU_RUN, &ctx->sched_flags))
spu_activate(ctx, 0);
spu_release(ctx);
}
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU core dump code
*
* (C) Copyright 2006 IBM Corp.
*
* Author: Dwayne Grant McConnell <decimal@us.ibm.com>
*/
#include <linux/elf.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/list.h>
#include <linux/syscalls.h>
#include <linux/coredump.h>
#include <linux/binfmts.h>
#include <linux/uaccess.h>
#include "spufs.h"
static int spufs_ctx_note_size(struct spu_context *ctx, int dfd)
{
int i, sz, total = 0;
char *name;
char fullname[80];
for (i = 0; spufs_coredump_read[i].name != NULL; i++) {
name = spufs_coredump_read[i].name;
sz = spufs_coredump_read[i].size;
sprintf(fullname, "SPU/%d/%s", dfd, name);
total += sizeof(struct elf_note);
total += roundup(strlen(fullname) + 1, 4);
total += roundup(sz, 4);
}
return total;
}
static int match_context(const void *v, struct file *file, unsigned fd)
{
struct spu_context *ctx;
if (file->f_op != &spufs_context_fops)
return 0;
ctx = SPUFS_I(file_inode(file))->i_ctx;
if (ctx->flags & SPU_CREATE_NOSCHED)
return 0;
return fd + 1;
}
/*
* The additional architecture-specific notes for Cell are various
* context files in the spu context.
*
* This function iterates over all open file descriptors and sees
* if they are a directory in spufs. In that case we use spufs
* internal functionality to dump them without needing to actually
* open the files.
*/
/*
* descriptor table is not shared, so files can't change or go away.
*/
static struct spu_context *coredump_next_context(int *fd)
{
struct spu_context *ctx = NULL;
struct file *file;
int n = iterate_fd(current->files, *fd, match_context, NULL);
if (!n)
return NULL;
*fd = n - 1;
file = fget_raw(*fd);
if (file) {
ctx = SPUFS_I(file_inode(file))->i_ctx;
get_spu_context(ctx);
fput(file);
}
return ctx;
}
int spufs_coredump_extra_notes_size(void)
{
struct spu_context *ctx;
int size = 0, rc, fd;
fd = 0;
while ((ctx = coredump_next_context(&fd)) != NULL) {
rc = spu_acquire_saved(ctx);
if (rc) {
put_spu_context(ctx);
break;
}
rc = spufs_ctx_note_size(ctx, fd);
spu_release_saved(ctx);
if (rc < 0) {
put_spu_context(ctx);
break;
}
size += rc;
/* start searching the next fd next time */
fd++;
put_spu_context(ctx);
}
return size;
}
static int spufs_arch_write_note(struct spu_context *ctx, int i,
struct coredump_params *cprm, int dfd)
{
size_t sz = spufs_coredump_read[i].size;
char fullname[80];
struct elf_note en;
int ret;
sprintf(fullname, "SPU/%d/%s", dfd, spufs_coredump_read[i].name);
en.n_namesz = strlen(fullname) + 1;
en.n_descsz = sz;
en.n_type = NT_SPU;
if (!dump_emit(cprm, &en, sizeof(en)))
return -EIO;
if (!dump_emit(cprm, fullname, en.n_namesz))
return -EIO;
if (!dump_align(cprm, 4))
return -EIO;
if (spufs_coredump_read[i].dump) {
ret = spufs_coredump_read[i].dump(ctx, cprm);
if (ret < 0)
return ret;
} else {
char buf[32];
ret = snprintf(buf, sizeof(buf), "0x%.16llx",
spufs_coredump_read[i].get(ctx));
if (ret >= sizeof(buf))
return sizeof(buf);
/* count trailing the NULL: */
if (!dump_emit(cprm, buf, ret + 1))
return -EIO;
}
dump_skip_to(cprm, roundup(cprm->pos - ret + sz, 4));
return 0;
}
int spufs_coredump_extra_notes_write(struct coredump_params *cprm)
{
struct spu_context *ctx;
int fd, j, rc;
fd = 0;
while ((ctx = coredump_next_context(&fd)) != NULL) {
rc = spu_acquire_saved(ctx);
if (rc)
return rc;
for (j = 0; spufs_coredump_read[j].name != NULL; j++) {
rc = spufs_arch_write_note(ctx, j, cprm, fd);
if (rc) {
spu_release_saved(ctx);
return rc;
}
}
spu_release_saved(ctx);
/* start searching the next fd next time */
fd++;
}
return 0;
}
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Low-level SPU handling
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#include <linux/sched/signal.h>
#include <linux/mm.h>
#include <asm/spu.h>
#include <asm/spu_csa.h>
#include "spufs.h"
/**
* Handle an SPE event, depending on context SPU_CREATE_EVENTS_ENABLED flag.
*
* If the context was created with events, we just set the return event.
* Otherwise, send an appropriate signal to the process.
*/
static void spufs_handle_event(struct spu_context *ctx,
unsigned long ea, int type)
{
if (ctx->flags & SPU_CREATE_EVENTS_ENABLED) {
ctx->event_return |= type;
wake_up_all(&ctx->stop_wq);
return;
}
switch (type) {
case SPE_EVENT_INVALID_DMA:
force_sig_fault(SIGBUS, BUS_OBJERR, NULL);
break;
case SPE_EVENT_SPE_DATA_STORAGE:
ctx->ops->restart_dma(ctx);
force_sig_fault(SIGSEGV, SEGV_ACCERR, (void __user *)ea);
break;
case SPE_EVENT_DMA_ALIGNMENT:
/* DAR isn't set for an alignment fault :( */
force_sig_fault(SIGBUS, BUS_ADRALN, NULL);
break;
case SPE_EVENT_SPE_ERROR:
force_sig_fault(
SIGILL, ILL_ILLOPC,
(void __user *)(unsigned long)
ctx->ops->npc_read(ctx) - 4);
break;
}
}
int spufs_handle_class0(struct spu_context *ctx)
{
unsigned long stat = ctx->csa.class_0_pending & CLASS0_INTR_MASK;
if (likely(!stat))
return 0;
if (stat & CLASS0_DMA_ALIGNMENT_INTR)
spufs_handle_event(ctx, ctx->csa.class_0_dar,
SPE_EVENT_DMA_ALIGNMENT);
if (stat & CLASS0_INVALID_DMA_COMMAND_INTR)
spufs_handle_event(ctx, ctx->csa.class_0_dar,
SPE_EVENT_INVALID_DMA);
if (stat & CLASS0_SPU_ERROR_INTR)
spufs_handle_event(ctx, ctx->csa.class_0_dar,
SPE_EVENT_SPE_ERROR);
ctx->csa.class_0_pending = 0;
return -EIO;
}
/*
* bottom half handler for page faults, we can't do this from
* interrupt context, since we might need to sleep.
* we also need to give up the mutex so we can get scheduled
* out while waiting for the backing store.
*
* TODO: try calling hash_page from the interrupt handler first
* in order to speed up the easy case.
*/
int spufs_handle_class1(struct spu_context *ctx)
{
u64 ea, dsisr, access;
unsigned long flags;
vm_fault_t flt = 0;
int ret;
/*
* dar and dsisr get passed from the registers
* to the spu_context, to this function, but not
* back to the spu if it gets scheduled again.
*
* if we don't handle the fault for a saved context
* in time, we can still expect to get the same fault
* the immediately after the context restore.
*/
ea = ctx->csa.class_1_dar;
dsisr = ctx->csa.class_1_dsisr;
if (!(dsisr & (MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED)))
return 0;
spuctx_switch_state(ctx, SPU_UTIL_IOWAIT);
pr_debug("ctx %p: ea %016llx, dsisr %016llx state %d\n", ctx, ea,
dsisr, ctx->state);
ctx->stats.hash_flt++;
if (ctx->state == SPU_STATE_RUNNABLE)
ctx->spu->stats.hash_flt++;
/* we must not hold the lock when entering copro_handle_mm_fault */
spu_release(ctx);
access = (_PAGE_PRESENT | _PAGE_READ);
access |= (dsisr & MFC_DSISR_ACCESS_PUT) ? _PAGE_WRITE : 0UL;
local_irq_save(flags);
ret = hash_page(ea, access, 0x300, dsisr);
local_irq_restore(flags);
/* hashing failed, so try the actual fault handler */
if (ret)
ret = copro_handle_mm_fault(current->mm, ea, dsisr, &flt);
/*
* This is nasty: we need the state_mutex for all the bookkeeping even
* if the syscall was interrupted by a signal. ewww.
*/
mutex_lock(&ctx->state_mutex);
/*
* Clear dsisr under ctxt lock after handling the fault, so that
* time slicing will not preempt the context while the page fault
* handler is running. Context switch code removes mappings.
*/
ctx->csa.class_1_dar = ctx->csa.class_1_dsisr = 0;
/*
* If we handled the fault successfully and are in runnable
* state, restart the DMA.
* In case of unhandled error report the problem to user space.
*/
if (!ret) {
if (flt & VM_FAULT_MAJOR)
ctx->stats.maj_flt++;
else
ctx->stats.min_flt++;
if (ctx->state == SPU_STATE_RUNNABLE) {
if (flt & VM_FAULT_MAJOR)
ctx->spu->stats.maj_flt++;
else
ctx->spu->stats.min_flt++;
}
if (ctx->spu)
ctx->ops->restart_dma(ctx);
} else
spufs_handle_event(ctx, ea, SPE_EVENT_SPE_DATA_STORAGE);
spuctx_switch_state(ctx, SPU_UTIL_SYSTEM);
return ret;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU file system
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#include <linux/list.h>
#include <linux/slab.h>
#include "spufs.h"
struct spu_gang *alloc_spu_gang(void)
{
struct spu_gang *gang;
gang = kzalloc_obj(*gang);
if (!gang)
goto out;
kref_init(&gang->kref);
mutex_init(&gang->mutex);
mutex_init(&gang->aff_mutex);
INIT_LIST_HEAD(&gang->list);
INIT_LIST_HEAD(&gang->aff_list_head);
gang->alive = 1;
out:
return gang;
}
static void destroy_spu_gang(struct kref *kref)
{
struct spu_gang *gang;
gang = container_of(kref, struct spu_gang, kref);
WARN_ON(gang->contexts || !list_empty(&gang->list));
kfree(gang);
}
struct spu_gang *get_spu_gang(struct spu_gang *gang)
{
kref_get(&gang->kref);
return gang;
}
int put_spu_gang(struct spu_gang *gang)
{
return kref_put(&gang->kref, &destroy_spu_gang);
}
void spu_gang_add_ctx(struct spu_gang *gang, struct spu_context *ctx)
{
mutex_lock(&gang->mutex);
ctx->gang = get_spu_gang(gang);
list_add(&ctx->gang_list, &gang->list);
gang->contexts++;
mutex_unlock(&gang->mutex);
}
void spu_gang_remove_ctx(struct spu_gang *gang, struct spu_context *ctx)
{
mutex_lock(&gang->mutex);
WARN_ON(ctx->gang != gang);
if (!list_empty(&ctx->aff_list)) {
list_del_init(&ctx->aff_list);
gang->aff_flags &= ~AFF_OFFSETS_SET;
}
list_del_init(&ctx->gang_list);
gang->contexts--;
mutex_unlock(&gang->mutex);
put_spu_gang(gang);
}
@@ -0,0 +1,335 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* hw_ops.c - query/set operations on active SPU context.
*
* Copyright (C) IBM 2005
* Author: Mark Nutter <mnutter@us.ibm.com>
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/smp.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <asm/io.h>
#include <asm/spu.h>
#include <asm/spu_priv1.h>
#include <asm/spu_csa.h>
#include <asm/mmu_context.h>
#include "spufs.h"
static int spu_hw_mbox_read(struct spu_context *ctx, u32 * data)
{
struct spu *spu = ctx->spu;
struct spu_problem __iomem *prob = spu->problem;
u32 mbox_stat;
int ret = 0;
spin_lock_irq(&spu->register_lock);
mbox_stat = in_be32(&prob->mb_stat_R);
if (mbox_stat & 0x0000ff) {
*data = in_be32(&prob->pu_mb_R);
ret = 4;
}
spin_unlock_irq(&spu->register_lock);
return ret;
}
static u32 spu_hw_mbox_stat_read(struct spu_context *ctx)
{
return in_be32(&ctx->spu->problem->mb_stat_R);
}
static __poll_t spu_hw_mbox_stat_poll(struct spu_context *ctx, __poll_t events)
{
struct spu *spu = ctx->spu;
__poll_t ret = 0;
u32 stat;
spin_lock_irq(&spu->register_lock);
stat = in_be32(&spu->problem->mb_stat_R);
/* if the requested event is there, return the poll
mask, otherwise enable the interrupt to get notified,
but first mark any pending interrupts as done so
we don't get woken up unnecessarily */
if (events & (EPOLLIN | EPOLLRDNORM)) {
if (stat & 0xff0000)
ret |= EPOLLIN | EPOLLRDNORM;
else {
spu_int_stat_clear(spu, 2, CLASS2_MAILBOX_INTR);
spu_int_mask_or(spu, 2, CLASS2_ENABLE_MAILBOX_INTR);
}
}
if (events & (EPOLLOUT | EPOLLWRNORM)) {
if (stat & 0x00ff00)
ret = EPOLLOUT | EPOLLWRNORM;
else {
spu_int_stat_clear(spu, 2,
CLASS2_MAILBOX_THRESHOLD_INTR);
spu_int_mask_or(spu, 2,
CLASS2_ENABLE_MAILBOX_THRESHOLD_INTR);
}
}
spin_unlock_irq(&spu->register_lock);
return ret;
}
static int spu_hw_ibox_read(struct spu_context *ctx, u32 * data)
{
struct spu *spu = ctx->spu;
struct spu_problem __iomem *prob = spu->problem;
struct spu_priv2 __iomem *priv2 = spu->priv2;
int ret;
spin_lock_irq(&spu->register_lock);
if (in_be32(&prob->mb_stat_R) & 0xff0000) {
/* read the first available word */
*data = in_be64(&priv2->puint_mb_R);
ret = 4;
} else {
/* make sure we get woken up by the interrupt */
spu_int_mask_or(spu, 2, CLASS2_ENABLE_MAILBOX_INTR);
ret = 0;
}
spin_unlock_irq(&spu->register_lock);
return ret;
}
static int spu_hw_wbox_write(struct spu_context *ctx, u32 data)
{
struct spu *spu = ctx->spu;
struct spu_problem __iomem *prob = spu->problem;
int ret;
spin_lock_irq(&spu->register_lock);
if (in_be32(&prob->mb_stat_R) & 0x00ff00) {
/* we have space to write wbox_data to */
out_be32(&prob->spu_mb_W, data);
ret = 4;
} else {
/* make sure we get woken up by the interrupt when space
becomes available */
spu_int_mask_or(spu, 2, CLASS2_ENABLE_MAILBOX_THRESHOLD_INTR);
ret = 0;
}
spin_unlock_irq(&spu->register_lock);
return ret;
}
static void spu_hw_signal1_write(struct spu_context *ctx, u32 data)
{
out_be32(&ctx->spu->problem->signal_notify1, data);
}
static void spu_hw_signal2_write(struct spu_context *ctx, u32 data)
{
out_be32(&ctx->spu->problem->signal_notify2, data);
}
static void spu_hw_signal1_type_set(struct spu_context *ctx, u64 val)
{
struct spu *spu = ctx->spu;
struct spu_priv2 __iomem *priv2 = spu->priv2;
u64 tmp;
spin_lock_irq(&spu->register_lock);
tmp = in_be64(&priv2->spu_cfg_RW);
if (val)
tmp |= 1;
else
tmp &= ~1;
out_be64(&priv2->spu_cfg_RW, tmp);
spin_unlock_irq(&spu->register_lock);
}
static u64 spu_hw_signal1_type_get(struct spu_context *ctx)
{
return ((in_be64(&ctx->spu->priv2->spu_cfg_RW) & 1) != 0);
}
static void spu_hw_signal2_type_set(struct spu_context *ctx, u64 val)
{
struct spu *spu = ctx->spu;
struct spu_priv2 __iomem *priv2 = spu->priv2;
u64 tmp;
spin_lock_irq(&spu->register_lock);
tmp = in_be64(&priv2->spu_cfg_RW);
if (val)
tmp |= 2;
else
tmp &= ~2;
out_be64(&priv2->spu_cfg_RW, tmp);
spin_unlock_irq(&spu->register_lock);
}
static u64 spu_hw_signal2_type_get(struct spu_context *ctx)
{
return ((in_be64(&ctx->spu->priv2->spu_cfg_RW) & 2) != 0);
}
static u32 spu_hw_npc_read(struct spu_context *ctx)
{
return in_be32(&ctx->spu->problem->spu_npc_RW);
}
static void spu_hw_npc_write(struct spu_context *ctx, u32 val)
{
out_be32(&ctx->spu->problem->spu_npc_RW, val);
}
static u32 spu_hw_status_read(struct spu_context *ctx)
{
return in_be32(&ctx->spu->problem->spu_status_R);
}
static char *spu_hw_get_ls(struct spu_context *ctx)
{
return ctx->spu->local_store;
}
static void spu_hw_privcntl_write(struct spu_context *ctx, u64 val)
{
out_be64(&ctx->spu->priv2->spu_privcntl_RW, val);
}
static u32 spu_hw_runcntl_read(struct spu_context *ctx)
{
return in_be32(&ctx->spu->problem->spu_runcntl_RW);
}
static void spu_hw_runcntl_write(struct spu_context *ctx, u32 val)
{
spin_lock_irq(&ctx->spu->register_lock);
if (val & SPU_RUNCNTL_ISOLATE)
spu_hw_privcntl_write(ctx,
SPU_PRIVCNT_LOAD_REQUEST_ENABLE_MASK);
out_be32(&ctx->spu->problem->spu_runcntl_RW, val);
spin_unlock_irq(&ctx->spu->register_lock);
}
static void spu_hw_runcntl_stop(struct spu_context *ctx)
{
spin_lock_irq(&ctx->spu->register_lock);
out_be32(&ctx->spu->problem->spu_runcntl_RW, SPU_RUNCNTL_STOP);
while (in_be32(&ctx->spu->problem->spu_status_R) & SPU_STATUS_RUNNING)
cpu_relax();
spin_unlock_irq(&ctx->spu->register_lock);
}
static void spu_hw_master_start(struct spu_context *ctx)
{
struct spu *spu = ctx->spu;
u64 sr1;
spin_lock_irq(&spu->register_lock);
sr1 = spu_mfc_sr1_get(spu) | MFC_STATE1_MASTER_RUN_CONTROL_MASK;
spu_mfc_sr1_set(spu, sr1);
spin_unlock_irq(&spu->register_lock);
}
static void spu_hw_master_stop(struct spu_context *ctx)
{
struct spu *spu = ctx->spu;
u64 sr1;
spin_lock_irq(&spu->register_lock);
sr1 = spu_mfc_sr1_get(spu) & ~MFC_STATE1_MASTER_RUN_CONTROL_MASK;
spu_mfc_sr1_set(spu, sr1);
spin_unlock_irq(&spu->register_lock);
}
static int spu_hw_set_mfc_query(struct spu_context * ctx, u32 mask, u32 mode)
{
struct spu_problem __iomem *prob = ctx->spu->problem;
int ret;
spin_lock_irq(&ctx->spu->register_lock);
ret = -EAGAIN;
if (in_be32(&prob->dma_querytype_RW))
goto out;
ret = 0;
out_be32(&prob->dma_querymask_RW, mask);
out_be32(&prob->dma_querytype_RW, mode);
out:
spin_unlock_irq(&ctx->spu->register_lock);
return ret;
}
static u32 spu_hw_read_mfc_tagstatus(struct spu_context * ctx)
{
return in_be32(&ctx->spu->problem->dma_tagstatus_R);
}
static u32 spu_hw_get_mfc_free_elements(struct spu_context *ctx)
{
return in_be32(&ctx->spu->problem->dma_qstatus_R);
}
static int spu_hw_send_mfc_command(struct spu_context *ctx,
struct mfc_dma_command *cmd)
{
u32 status;
struct spu_problem __iomem *prob = ctx->spu->problem;
spin_lock_irq(&ctx->spu->register_lock);
out_be32(&prob->mfc_lsa_W, cmd->lsa);
out_be64(&prob->mfc_ea_W, cmd->ea);
out_be32(&prob->mfc_union_W.by32.mfc_size_tag32,
cmd->size << 16 | cmd->tag);
out_be32(&prob->mfc_union_W.by32.mfc_class_cmd32,
cmd->class << 16 | cmd->cmd);
status = in_be32(&prob->mfc_union_W.by32.mfc_class_cmd32);
spin_unlock_irq(&ctx->spu->register_lock);
switch (status & 0xffff) {
case 0:
return 0;
case 2:
return -EAGAIN;
default:
return -EINVAL;
}
}
static void spu_hw_restart_dma(struct spu_context *ctx)
{
struct spu_priv2 __iomem *priv2 = ctx->spu->priv2;
if (!test_bit(SPU_CONTEXT_SWITCH_PENDING, &ctx->spu->flags))
out_be64(&priv2->mfc_control_RW, MFC_CNTL_RESTART_DMA_COMMAND);
}
struct spu_context_ops spu_hw_ops = {
.mbox_read = spu_hw_mbox_read,
.mbox_stat_read = spu_hw_mbox_stat_read,
.mbox_stat_poll = spu_hw_mbox_stat_poll,
.ibox_read = spu_hw_ibox_read,
.wbox_write = spu_hw_wbox_write,
.signal1_write = spu_hw_signal1_write,
.signal2_write = spu_hw_signal2_write,
.signal1_type_set = spu_hw_signal1_type_set,
.signal1_type_get = spu_hw_signal1_type_get,
.signal2_type_set = spu_hw_signal2_type_set,
.signal2_type_get = spu_hw_signal2_type_get,
.npc_read = spu_hw_npc_read,
.npc_write = spu_hw_npc_write,
.status_read = spu_hw_status_read,
.get_ls = spu_hw_get_ls,
.privcntl_write = spu_hw_privcntl_write,
.runcntl_read = spu_hw_runcntl_read,
.runcntl_write = spu_hw_runcntl_write,
.runcntl_stop = spu_hw_runcntl_stop,
.master_start = spu_hw_master_start,
.master_stop = spu_hw_master_stop,
.set_mfc_query = spu_hw_set_mfc_query,
.read_mfc_tagstatus = spu_hw_read_mfc_tagstatus,
.get_mfc_free_elements = spu_hw_get_mfc_free_elements,
.send_mfc_command = spu_hw_send_mfc_command,
.restart_dma = spu_hw_restart_dma,
};
@@ -0,0 +1,815 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU file system
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/fsnotify.h>
#include <linux/backing-dev.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/pagemap.h>
#include <linux/poll.h>
#include <linux/of.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <asm/spu.h>
#include <asm/spu_priv1.h>
#include <linux/uaccess.h>
#include "spufs.h"
struct spufs_sb_info {
bool debug;
};
static struct kmem_cache *spufs_inode_cache;
char *isolated_loader;
static int isolated_loader_size;
static struct spufs_sb_info *spufs_get_sb_info(struct super_block *sb)
{
return sb->s_fs_info;
}
static struct inode *
spufs_alloc_inode(struct super_block *sb)
{
struct spufs_inode_info *ei;
ei = kmem_cache_alloc(spufs_inode_cache, GFP_KERNEL);
if (!ei)
return NULL;
ei->i_gang = NULL;
ei->i_ctx = NULL;
ei->i_openers = 0;
return &ei->vfs_inode;
}
static void spufs_free_inode(struct inode *inode)
{
kmem_cache_free(spufs_inode_cache, SPUFS_I(inode));
}
static void
spufs_init_once(void *p)
{
struct spufs_inode_info *ei = p;
inode_init_once(&ei->vfs_inode);
}
static struct inode *
spufs_new_inode(struct super_block *sb, umode_t mode)
{
struct inode *inode;
inode = new_inode(sb);
if (!inode)
goto out;
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_uid = current_fsuid();
inode->i_gid = current_fsgid();
simple_inode_init_ts(inode);
out:
return inode;
}
static int
spufs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
if ((attr->ia_valid & ATTR_SIZE) &&
(attr->ia_size != inode->i_size))
return -EINVAL;
setattr_copy(&nop_mnt_idmap, inode, attr);
mark_inode_dirty(inode);
return 0;
}
static int
spufs_new_file(struct super_block *sb, struct dentry *dentry,
const struct file_operations *fops, umode_t mode,
size_t size, struct spu_context *ctx)
{
static const struct inode_operations spufs_file_iops = {
.setattr = spufs_setattr,
};
struct inode *inode;
int ret;
ret = -ENOSPC;
inode = spufs_new_inode(sb, S_IFREG | mode);
if (!inode)
goto out;
ret = 0;
inode->i_op = &spufs_file_iops;
inode->i_fop = fops;
inode->i_size = size;
inode->i_private = SPUFS_I(inode)->i_ctx = get_spu_context(ctx);
d_make_persistent(dentry, inode);
out:
return ret;
}
static void
spufs_evict_inode(struct inode *inode)
{
struct spufs_inode_info *ei = SPUFS_I(inode);
clear_inode(inode);
if (ei->i_ctx)
put_spu_context(ei->i_ctx);
if (ei->i_gang)
put_spu_gang(ei->i_gang);
}
/* Caller must hold parent->i_mutex */
static void spufs_rmdir(struct inode *parent, struct dentry *dir)
{
struct spu_context *ctx = SPUFS_I(d_inode(dir))->i_ctx;
locked_recursive_removal(dir, NULL);
spu_forget(ctx);
}
static int spufs_fill_dir(struct dentry *dir,
const struct spufs_tree_descr *files, umode_t mode,
struct spu_context *ctx)
{
while (files->name && files->name[0]) {
int ret;
struct dentry *dentry = d_alloc_name(dir, files->name);
if (!dentry)
return -ENOMEM;
ret = spufs_new_file(dir->d_sb, dentry, files->ops,
files->mode & mode, files->size, ctx);
dput(dentry);
if (ret)
return ret;
files++;
}
return 0;
}
static void unuse_gang(struct dentry *dir)
{
struct inode *inode = dir->d_inode;
struct spu_gang *gang = SPUFS_I(inode)->i_gang;
if (gang) {
bool dead;
inode_lock(inode); // exclusion with spufs_create_context()
dead = !--gang->alive;
inode_unlock(inode);
if (dead)
simple_recursive_removal(dir, NULL);
}
}
static int spufs_dir_close(struct inode *inode, struct file *file)
{
struct inode *parent;
struct dentry *dir;
dir = file->f_path.dentry;
parent = d_inode(dir->d_parent);
inode_lock_nested(parent, I_MUTEX_PARENT);
spufs_rmdir(parent, dir);
inode_unlock(parent);
unuse_gang(dir->d_parent);
return dcache_dir_close(inode, file);
}
const struct file_operations spufs_context_fops = {
.open = dcache_dir_open,
.release = spufs_dir_close,
.llseek = dcache_dir_lseek,
.read = generic_read_dir,
.iterate_shared = dcache_readdir,
.fsync = noop_fsync,
};
EXPORT_SYMBOL_GPL(spufs_context_fops);
static int
spufs_mkdir(struct inode *dir, struct dentry *dentry, unsigned int flags,
umode_t mode)
{
int ret;
struct inode *inode;
struct spu_context *ctx;
inode = spufs_new_inode(dir->i_sb, mode | S_IFDIR);
if (!inode)
return -ENOSPC;
inode_init_owner(&nop_mnt_idmap, inode, dir, mode | S_IFDIR);
ctx = alloc_spu_context(SPUFS_I(dir)->i_gang); /* XXX gang */
SPUFS_I(inode)->i_ctx = ctx;
if (!ctx) {
iput(inode);
return -ENOSPC;
}
ctx->flags = flags;
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inode_lock(inode);
inc_nlink(dir);
inc_nlink(inode);
d_make_persistent(dentry, inode);
if (flags & SPU_CREATE_NOSCHED)
ret = spufs_fill_dir(dentry, spufs_dir_nosched_contents,
mode, ctx);
else
ret = spufs_fill_dir(dentry, spufs_dir_contents, mode, ctx);
if (!ret && spufs_get_sb_info(dir->i_sb)->debug)
ret = spufs_fill_dir(dentry, spufs_dir_debug_contents,
mode, ctx);
inode_unlock(inode);
if (ret)
spufs_rmdir(dir, dentry);
return ret;
}
static int spufs_context_open(const struct path *path)
{
FD_PREPARE(fdf, 0, dentry_open(path, O_RDONLY, current_cred()));
if (fdf.err)
return fdf.err;
fd_prepare_file(fdf)->f_op = &spufs_context_fops;
return fd_publish(fdf);
}
static struct spu_context *
spufs_assert_affinity(unsigned int flags, struct spu_gang *gang,
struct file *filp)
{
struct spu_context *tmp, *neighbor, *err;
int count, node;
int aff_supp;
aff_supp = !list_empty(&(list_entry(cbe_spu_info[0].spus.next,
struct spu, cbe_list))->aff_list);
if (!aff_supp)
return ERR_PTR(-EINVAL);
if (flags & SPU_CREATE_GANG)
return ERR_PTR(-EINVAL);
if (flags & SPU_CREATE_AFFINITY_MEM &&
gang->aff_ref_ctx &&
gang->aff_ref_ctx->flags & SPU_CREATE_AFFINITY_MEM)
return ERR_PTR(-EEXIST);
if (gang->aff_flags & AFF_MERGED)
return ERR_PTR(-EBUSY);
neighbor = NULL;
if (flags & SPU_CREATE_AFFINITY_SPU) {
if (!filp || filp->f_op != &spufs_context_fops)
return ERR_PTR(-EINVAL);
neighbor = get_spu_context(
SPUFS_I(file_inode(filp))->i_ctx);
if (!list_empty(&neighbor->aff_list) && !(neighbor->aff_head) &&
!list_is_last(&neighbor->aff_list, &gang->aff_list_head) &&
!list_entry(neighbor->aff_list.next, struct spu_context,
aff_list)->aff_head) {
err = ERR_PTR(-EEXIST);
goto out_put_neighbor;
}
if (gang != neighbor->gang) {
err = ERR_PTR(-EINVAL);
goto out_put_neighbor;
}
count = 1;
list_for_each_entry(tmp, &gang->aff_list_head, aff_list)
count++;
if (list_empty(&neighbor->aff_list))
count++;
for (node = 0; node < MAX_NUMNODES; node++) {
if ((cbe_spu_info[node].n_spus - atomic_read(
&cbe_spu_info[node].reserved_spus)) >= count)
break;
}
if (node == MAX_NUMNODES) {
err = ERR_PTR(-EEXIST);
goto out_put_neighbor;
}
}
return neighbor;
out_put_neighbor:
put_spu_context(neighbor);
return err;
}
static void
spufs_set_affinity(unsigned int flags, struct spu_context *ctx,
struct spu_context *neighbor)
{
if (flags & SPU_CREATE_AFFINITY_MEM)
ctx->gang->aff_ref_ctx = ctx;
if (flags & SPU_CREATE_AFFINITY_SPU) {
if (list_empty(&neighbor->aff_list)) {
list_add_tail(&neighbor->aff_list,
&ctx->gang->aff_list_head);
neighbor->aff_head = 1;
}
if (list_is_last(&neighbor->aff_list, &ctx->gang->aff_list_head)
|| list_entry(neighbor->aff_list.next, struct spu_context,
aff_list)->aff_head) {
list_add(&ctx->aff_list, &neighbor->aff_list);
} else {
list_add_tail(&ctx->aff_list, &neighbor->aff_list);
if (neighbor->aff_head) {
neighbor->aff_head = 0;
ctx->aff_head = 1;
}
}
if (!ctx->gang->aff_ref_ctx)
ctx->gang->aff_ref_ctx = ctx;
}
}
static int
spufs_create_context(struct inode *inode, struct dentry *dentry,
struct vfsmount *mnt, int flags, umode_t mode,
struct file *aff_filp)
{
int ret;
int affinity;
struct spu_gang *gang = SPUFS_I(inode)->i_gang;
struct spu_context *neighbor;
struct path path = {.mnt = mnt, .dentry = dentry};
if ((flags & SPU_CREATE_NOSCHED) &&
!capable(CAP_SYS_NICE))
return -EPERM;
if ((flags & (SPU_CREATE_NOSCHED | SPU_CREATE_ISOLATE))
== SPU_CREATE_ISOLATE)
return -EINVAL;
if ((flags & SPU_CREATE_ISOLATE) && !isolated_loader)
return -ENODEV;
if (gang) {
if (!gang->alive)
return -ENOENT;
gang->alive++;
}
neighbor = NULL;
affinity = flags & (SPU_CREATE_AFFINITY_MEM | SPU_CREATE_AFFINITY_SPU);
if (affinity) {
if (!gang)
return -EINVAL;
mutex_lock(&gang->aff_mutex);
neighbor = spufs_assert_affinity(flags, gang, aff_filp);
if (IS_ERR(neighbor)) {
ret = PTR_ERR(neighbor);
goto out_aff_unlock;
}
}
ret = spufs_mkdir(inode, dentry, flags, mode & 0777);
if (ret) {
if (neighbor)
put_spu_context(neighbor);
goto out_aff_unlock;
}
if (affinity) {
spufs_set_affinity(flags, SPUFS_I(d_inode(dentry))->i_ctx,
neighbor);
if (neighbor)
put_spu_context(neighbor);
}
ret = spufs_context_open(&path);
if (ret < 0)
spufs_rmdir(inode, dentry);
out_aff_unlock:
if (affinity)
mutex_unlock(&gang->aff_mutex);
if (ret && gang)
gang->alive--; // can't reach 0
return ret;
}
static int
spufs_mkgang(struct inode *dir, struct dentry *dentry, umode_t mode)
{
int ret;
struct inode *inode;
struct spu_gang *gang;
ret = -ENOSPC;
inode = spufs_new_inode(dir->i_sb, mode | S_IFDIR);
if (!inode)
goto out;
ret = 0;
inode_init_owner(&nop_mnt_idmap, inode, dir, mode | S_IFDIR);
gang = alloc_spu_gang();
SPUFS_I(inode)->i_ctx = NULL;
SPUFS_I(inode)->i_gang = gang;
if (!gang) {
ret = -ENOMEM;
goto out_iput;
}
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(dir);
inc_nlink(inode);
d_make_persistent(dentry, inode);
return ret;
out_iput:
iput(inode);
out:
return ret;
}
static int spufs_gang_close(struct inode *inode, struct file *file)
{
unuse_gang(file->f_path.dentry);
return dcache_dir_close(inode, file);
}
static const struct file_operations spufs_gang_fops = {
.open = dcache_dir_open,
.release = spufs_gang_close,
.llseek = dcache_dir_lseek,
.read = generic_read_dir,
.iterate_shared = dcache_readdir,
.fsync = noop_fsync,
};
static int spufs_gang_open(const struct path *path)
{
/*
* get references for dget and mntget, will be released
* in error path of *_open().
*/
FD_PREPARE(fdf, 0, dentry_open(path, O_RDONLY, current_cred()));
if (fdf.err)
return fdf.err;
fd_prepare_file(fdf)->f_op = &spufs_gang_fops;
return fd_publish(fdf);
}
static int spufs_create_gang(struct inode *inode,
struct dentry *dentry,
struct vfsmount *mnt, umode_t mode)
{
struct path path = {.mnt = mnt, .dentry = dentry};
int ret;
ret = spufs_mkgang(inode, dentry, mode & 0777);
if (!ret) {
ret = spufs_gang_open(&path);
if (ret < 0)
unuse_gang(dentry);
}
return ret;
}
static struct file_system_type spufs_type;
long spufs_create(const struct path *path, struct dentry *dentry,
unsigned int flags, umode_t mode, struct file *filp)
{
struct inode *dir = d_inode(path->dentry);
int ret;
/* check if we are on spufs */
if (path->dentry->d_sb->s_type != &spufs_type)
return -EINVAL;
/* don't accept undefined flags */
if (flags & (~SPU_CREATE_FLAG_ALL))
return -EINVAL;
/* only threads can be underneath a gang */
if (path->dentry != path->dentry->d_sb->s_root)
if ((flags & SPU_CREATE_GANG) || !SPUFS_I(dir)->i_gang)
return -EINVAL;
mode &= ~current_umask();
if (flags & SPU_CREATE_GANG)
ret = spufs_create_gang(dir, dentry, path->mnt, mode);
else
ret = spufs_create_context(dir, dentry, path->mnt, flags, mode,
filp);
if (ret >= 0)
fsnotify_mkdir(dir, dentry);
return ret;
}
/* File system initialization */
struct spufs_fs_context {
kuid_t uid;
kgid_t gid;
umode_t mode;
};
enum {
Opt_uid, Opt_gid, Opt_mode, Opt_debug,
};
static const struct fs_parameter_spec spufs_fs_parameters[] = {
fsparam_u32 ("gid", Opt_gid),
fsparam_u32oct ("mode", Opt_mode),
fsparam_u32 ("uid", Opt_uid),
fsparam_flag ("debug", Opt_debug),
{}
};
static int spufs_show_options(struct seq_file *m, struct dentry *root)
{
struct spufs_sb_info *sbi = spufs_get_sb_info(root->d_sb);
struct inode *inode = root->d_inode;
if (!uid_eq(inode->i_uid, GLOBAL_ROOT_UID))
seq_printf(m, ",uid=%u",
from_kuid_munged(&init_user_ns, inode->i_uid));
if (!gid_eq(inode->i_gid, GLOBAL_ROOT_GID))
seq_printf(m, ",gid=%u",
from_kgid_munged(&init_user_ns, inode->i_gid));
if ((inode->i_mode & S_IALLUGO) != 0775)
seq_printf(m, ",mode=%o", inode->i_mode);
if (sbi->debug)
seq_puts(m, ",debug");
return 0;
}
static int spufs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct spufs_fs_context *ctx = fc->fs_private;
struct spufs_sb_info *sbi = fc->s_fs_info;
struct fs_parse_result result;
kuid_t uid;
kgid_t gid;
int opt;
opt = fs_parse(fc, spufs_fs_parameters, param, &result);
if (opt < 0)
return opt;
switch (opt) {
case Opt_uid:
uid = make_kuid(current_user_ns(), result.uint_32);
if (!uid_valid(uid))
return invalf(fc, "Unknown uid");
ctx->uid = uid;
break;
case Opt_gid:
gid = make_kgid(current_user_ns(), result.uint_32);
if (!gid_valid(gid))
return invalf(fc, "Unknown gid");
ctx->gid = gid;
break;
case Opt_mode:
ctx->mode = result.uint_32 & S_IALLUGO;
break;
case Opt_debug:
sbi->debug = true;
break;
}
return 0;
}
static void spufs_exit_isolated_loader(void)
{
free_pages((unsigned long) isolated_loader,
get_order(isolated_loader_size));
}
static void __init
spufs_init_isolated_loader(void)
{
struct device_node *dn;
const char *loader;
int size;
dn = of_find_node_by_path("/spu-isolation");
if (!dn)
return;
loader = of_get_property(dn, "loader", &size);
of_node_put(dn);
if (!loader)
return;
/* the loader must be align on a 16 byte boundary */
isolated_loader = (char *)__get_free_pages(GFP_KERNEL, get_order(size));
if (!isolated_loader)
return;
isolated_loader_size = size;
memcpy(isolated_loader, loader, size);
printk(KERN_INFO "spufs: SPU isolation mode enabled\n");
}
static int spufs_create_root(struct super_block *sb, struct fs_context *fc)
{
struct spufs_fs_context *ctx = fc->fs_private;
struct inode *inode;
if (!spu_management_ops)
return -ENODEV;
inode = spufs_new_inode(sb, S_IFDIR | ctx->mode);
if (!inode)
return -ENOMEM;
inode->i_uid = ctx->uid;
inode->i_gid = ctx->gid;
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
SPUFS_I(inode)->i_ctx = NULL;
inc_nlink(inode);
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;
return 0;
}
static const struct super_operations spufs_ops = {
.alloc_inode = spufs_alloc_inode,
.free_inode = spufs_free_inode,
.statfs = simple_statfs,
.evict_inode = spufs_evict_inode,
.show_options = spufs_show_options,
};
static int spufs_fill_super(struct super_block *sb, struct fs_context *fc)
{
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = SPUFS_MAGIC;
sb->s_op = &spufs_ops;
return spufs_create_root(sb, fc);
}
static int spufs_get_tree(struct fs_context *fc)
{
return get_tree_single(fc, spufs_fill_super);
}
static void spufs_free_fc(struct fs_context *fc)
{
kfree(fc->s_fs_info);
}
static const struct fs_context_operations spufs_context_ops = {
.free = spufs_free_fc,
.parse_param = spufs_parse_param,
.get_tree = spufs_get_tree,
};
static int spufs_init_fs_context(struct fs_context *fc)
{
struct spufs_fs_context *ctx;
struct spufs_sb_info *sbi;
ctx = kzalloc_obj(struct spufs_fs_context);
if (!ctx)
goto nomem;
sbi = kzalloc_obj(struct spufs_sb_info);
if (!sbi)
goto nomem_ctx;
ctx->uid = current_uid();
ctx->gid = current_gid();
ctx->mode = 0755;
fc->fs_private = ctx;
fc->s_fs_info = sbi;
fc->ops = &spufs_context_ops;
return 0;
nomem_ctx:
kfree(ctx);
nomem:
return -ENOMEM;
}
static struct file_system_type spufs_type = {
.owner = THIS_MODULE,
.name = "spufs",
.init_fs_context = spufs_init_fs_context,
.parameters = spufs_fs_parameters,
.kill_sb = kill_anon_super,
};
MODULE_ALIAS_FS("spufs");
static int __init spufs_init(void)
{
int ret;
ret = -ENODEV;
if (!spu_management_ops)
goto out;
ret = -ENOMEM;
spufs_inode_cache = kmem_cache_create("spufs_inode_cache",
sizeof(struct spufs_inode_info), 0,
SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT, spufs_init_once);
if (!spufs_inode_cache)
goto out;
ret = spu_sched_init();
if (ret)
goto out_cache;
ret = register_spu_syscalls(&spufs_calls);
if (ret)
goto out_sched;
ret = register_filesystem(&spufs_type);
if (ret)
goto out_syscalls;
spufs_init_isolated_loader();
return 0;
out_syscalls:
unregister_spu_syscalls(&spufs_calls);
out_sched:
spu_sched_exit();
out_cache:
kmem_cache_destroy(spufs_inode_cache);
out:
return ret;
}
module_init(spufs_init);
static void __exit spufs_exit(void)
{
spu_sched_exit();
spufs_exit_isolated_loader();
unregister_spu_syscalls(&spufs_calls);
unregister_filesystem(&spufs_type);
kmem_cache_destroy(spufs_inode_cache);
}
module_exit(spufs_exit);
MODULE_DESCRIPTION("SPU file system");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* SPU local store allocation routines
*
* Copyright 2007 Benjamin Herrenschmidt, IBM Corp.
*/
#undef DEBUG
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/spu.h>
#include <asm/spu_csa.h>
#include <asm/mmu.h>
#include "spufs.h"
int spu_alloc_lscsa(struct spu_state *csa)
{
struct spu_lscsa *lscsa;
unsigned char *p;
lscsa = vzalloc(sizeof(*lscsa));
if (!lscsa)
return -ENOMEM;
csa->lscsa = lscsa;
/* Set LS pages reserved to allow for user-space mapping. */
for (p = lscsa->ls; p < lscsa->ls + LS_SIZE; p += PAGE_SIZE)
SetPageReserved(vmalloc_to_page(p));
return 0;
}
void spu_free_lscsa(struct spu_state *csa)
{
/* Clear reserved bit before vfree. */
unsigned char *p;
if (csa->lscsa == NULL)
return;
for (p = csa->lscsa->ls; p < csa->lscsa->ls + LS_SIZE; p += PAGE_SIZE)
ClearPageReserved(vmalloc_to_page(p));
vfree(csa->lscsa);
}
@@ -0,0 +1,451 @@
// SPDX-License-Identifier: GPL-2.0
#define DEBUG
#include <linux/wait.h>
#include <linux/ptrace.h>
#include <asm/spu.h>
#include <asm/spu_priv1.h>
#include <asm/io.h>
#include <asm/unistd.h>
#include "spufs.h"
/* interrupt-level stop callback function. */
void spufs_stop_callback(struct spu *spu, int irq)
{
struct spu_context *ctx = spu->ctx;
/*
* It should be impossible to preempt a context while an exception
* is being processed, since the context switch code is specially
* coded to deal with interrupts ... But, just in case, sanity check
* the context pointer. It is OK to return doing nothing since
* the exception will be regenerated when the context is resumed.
*/
if (ctx) {
/* Copy exception arguments into module specific structure */
switch(irq) {
case 0 :
ctx->csa.class_0_pending = spu->class_0_pending;
ctx->csa.class_0_dar = spu->class_0_dar;
break;
case 1 :
ctx->csa.class_1_dsisr = spu->class_1_dsisr;
ctx->csa.class_1_dar = spu->class_1_dar;
break;
case 2 :
break;
}
/* ensure that the exception status has hit memory before a
* thread waiting on the context's stop queue is woken */
smp_wmb();
wake_up_all(&ctx->stop_wq);
}
}
int spu_stopped(struct spu_context *ctx, u32 *stat)
{
u64 dsisr;
u32 stopped;
stopped = SPU_STATUS_INVALID_INSTR | SPU_STATUS_SINGLE_STEP |
SPU_STATUS_STOPPED_BY_HALT | SPU_STATUS_STOPPED_BY_STOP;
top:
*stat = ctx->ops->status_read(ctx);
if (*stat & stopped) {
/*
* If the spu hasn't finished stopping, we need to
* re-read the register to get the stopped value.
*/
if (*stat & SPU_STATUS_RUNNING)
goto top;
return 1;
}
if (test_bit(SPU_SCHED_NOTIFY_ACTIVE, &ctx->sched_flags))
return 1;
dsisr = ctx->csa.class_1_dsisr;
if (dsisr & (MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED))
return 1;
if (ctx->csa.class_0_pending)
return 1;
return 0;
}
static int spu_setup_isolated(struct spu_context *ctx)
{
int ret;
u64 __iomem *mfc_cntl;
u64 sr1;
u32 status;
unsigned long timeout;
const u32 status_loading = SPU_STATUS_RUNNING
| SPU_STATUS_ISOLATED_STATE | SPU_STATUS_ISOLATED_LOAD_STATUS;
ret = -ENODEV;
if (!isolated_loader)
goto out;
/*
* We need to exclude userspace access to the context.
*
* To protect against memory access we invalidate all ptes
* and make sure the pagefault handlers block on the mutex.
*/
spu_unmap_mappings(ctx);
mfc_cntl = &ctx->spu->priv2->mfc_control_RW;
/* purge the MFC DMA queue to ensure no spurious accesses before we
* enter kernel mode */
timeout = jiffies + HZ;
out_be64(mfc_cntl, MFC_CNTL_PURGE_DMA_REQUEST);
while ((in_be64(mfc_cntl) & MFC_CNTL_PURGE_DMA_STATUS_MASK)
!= MFC_CNTL_PURGE_DMA_COMPLETE) {
if (time_after(jiffies, timeout)) {
printk(KERN_ERR "%s: timeout flushing MFC DMA queue\n",
__func__);
ret = -EIO;
goto out;
}
cond_resched();
}
/* clear purge status */
out_be64(mfc_cntl, 0);
/* put the SPE in kernel mode to allow access to the loader */
sr1 = spu_mfc_sr1_get(ctx->spu);
sr1 &= ~MFC_STATE1_PROBLEM_STATE_MASK;
spu_mfc_sr1_set(ctx->spu, sr1);
/* start the loader */
ctx->ops->signal1_write(ctx, (unsigned long)isolated_loader >> 32);
ctx->ops->signal2_write(ctx,
(unsigned long)isolated_loader & 0xffffffff);
ctx->ops->runcntl_write(ctx,
SPU_RUNCNTL_RUNNABLE | SPU_RUNCNTL_ISOLATE);
ret = 0;
timeout = jiffies + HZ;
while (((status = ctx->ops->status_read(ctx)) & status_loading) ==
status_loading) {
if (time_after(jiffies, timeout)) {
printk(KERN_ERR "%s: timeout waiting for loader\n",
__func__);
ret = -EIO;
goto out_drop_priv;
}
cond_resched();
}
if (!(status & SPU_STATUS_RUNNING)) {
/* If isolated LOAD has failed: run SPU, we will get a stop-and
* signal later. */
pr_debug("%s: isolated LOAD failed\n", __func__);
ctx->ops->runcntl_write(ctx, SPU_RUNCNTL_RUNNABLE);
ret = -EACCES;
goto out_drop_priv;
}
if (!(status & SPU_STATUS_ISOLATED_STATE)) {
/* This isn't allowed by the CBEA, but check anyway */
pr_debug("%s: SPU fell out of isolated mode?\n", __func__);
ctx->ops->runcntl_write(ctx, SPU_RUNCNTL_STOP);
ret = -EINVAL;
goto out_drop_priv;
}
out_drop_priv:
/* Finished accessing the loader. Drop kernel mode */
sr1 |= MFC_STATE1_PROBLEM_STATE_MASK;
spu_mfc_sr1_set(ctx->spu, sr1);
out:
return ret;
}
static int spu_run_init(struct spu_context *ctx, u32 *npc)
{
unsigned long runcntl = SPU_RUNCNTL_RUNNABLE;
int ret;
spuctx_switch_state(ctx, SPU_UTIL_SYSTEM);
/*
* NOSCHED is synchronous scheduling with respect to the caller.
* The caller waits for the context to be loaded.
*/
if (ctx->flags & SPU_CREATE_NOSCHED) {
if (ctx->state == SPU_STATE_SAVED) {
ret = spu_activate(ctx, 0);
if (ret)
return ret;
}
}
/*
* Apply special setup as required.
*/
if (ctx->flags & SPU_CREATE_ISOLATE) {
if (!(ctx->ops->status_read(ctx) & SPU_STATUS_ISOLATED_STATE)) {
ret = spu_setup_isolated(ctx);
if (ret)
return ret;
}
/*
* If userspace has set the runcntrl register (eg, to
* issue an isolated exit), we need to re-set it here
*/
runcntl = ctx->ops->runcntl_read(ctx) &
(SPU_RUNCNTL_RUNNABLE | SPU_RUNCNTL_ISOLATE);
if (runcntl == 0)
runcntl = SPU_RUNCNTL_RUNNABLE;
} else {
unsigned long privcntl;
if (test_thread_flag(TIF_SINGLESTEP))
privcntl = SPU_PRIVCNTL_MODE_SINGLE_STEP;
else
privcntl = SPU_PRIVCNTL_MODE_NORMAL;
ctx->ops->privcntl_write(ctx, privcntl);
ctx->ops->npc_write(ctx, *npc);
}
ctx->ops->runcntl_write(ctx, runcntl);
if (ctx->flags & SPU_CREATE_NOSCHED) {
spuctx_switch_state(ctx, SPU_UTIL_USER);
} else {
if (ctx->state == SPU_STATE_SAVED) {
ret = spu_activate(ctx, 0);
if (ret)
return ret;
} else {
spuctx_switch_state(ctx, SPU_UTIL_USER);
}
}
set_bit(SPU_SCHED_SPU_RUN, &ctx->sched_flags);
return 0;
}
static int spu_run_fini(struct spu_context *ctx, u32 *npc,
u32 *status)
{
int ret = 0;
spu_del_from_rq(ctx);
*status = ctx->ops->status_read(ctx);
*npc = ctx->ops->npc_read(ctx);
spuctx_switch_state(ctx, SPU_UTIL_IDLE_LOADED);
clear_bit(SPU_SCHED_SPU_RUN, &ctx->sched_flags);
spu_switch_log_notify(NULL, ctx, SWITCH_LOG_EXIT, *status);
spu_release(ctx);
if (signal_pending(current))
ret = -ERESTARTSYS;
return ret;
}
/*
* SPU syscall restarting is tricky because we violate the basic
* assumption that the signal handler is running on the interrupted
* thread. Here instead, the handler runs on PowerPC user space code,
* while the syscall was called from the SPU.
* This means we can only do a very rough approximation of POSIX
* signal semantics.
*/
static int spu_handle_restartsys(struct spu_context *ctx, long *spu_ret,
unsigned int *npc)
{
int ret;
switch (*spu_ret) {
case -ERESTARTSYS:
case -ERESTARTNOINTR:
/*
* Enter the regular syscall restarting for
* sys_spu_run, then restart the SPU syscall
* callback.
*/
*npc -= 8;
ret = -ERESTARTSYS;
break;
case -ERESTARTNOHAND:
case -ERESTART_RESTARTBLOCK:
/*
* Restart block is too hard for now, just return -EINTR
* to the SPU.
* ERESTARTNOHAND comes from sys_pause, we also return
* -EINTR from there.
* Assume that we need to be restarted ourselves though.
*/
*spu_ret = -EINTR;
ret = -ERESTARTSYS;
break;
default:
printk(KERN_WARNING "%s: unexpected return code %ld\n",
__func__, *spu_ret);
ret = 0;
}
return ret;
}
static int spu_process_callback(struct spu_context *ctx)
{
struct spu_syscall_block s;
u32 ls_pointer, npc;
void __iomem *ls;
long spu_ret;
int ret;
/* get syscall block from local store */
npc = ctx->ops->npc_read(ctx) & ~3;
ls = (void __iomem *)ctx->ops->get_ls(ctx);
ls_pointer = in_be32(ls + npc);
if (ls_pointer > (LS_SIZE - sizeof(s)))
return -EFAULT;
memcpy_fromio(&s, ls + ls_pointer, sizeof(s));
/* do actual syscall without pinning the spu */
ret = 0;
spu_ret = -ENOSYS;
npc += 4;
if (s.nr_ret < NR_syscalls) {
spu_release(ctx);
/* do actual system call from here */
spu_ret = spu_sys_callback(&s);
if (spu_ret <= -ERESTARTSYS) {
ret = spu_handle_restartsys(ctx, &spu_ret, &npc);
}
mutex_lock(&ctx->state_mutex);
if (ret == -ERESTARTSYS)
return ret;
}
/* need to re-get the ls, as it may have changed when we released the
* spu */
ls = (void __iomem *)ctx->ops->get_ls(ctx);
/* write result, jump over indirect pointer */
memcpy_toio(ls + ls_pointer, &spu_ret, sizeof(spu_ret));
ctx->ops->npc_write(ctx, npc);
ctx->ops->runcntl_write(ctx, SPU_RUNCNTL_RUNNABLE);
return ret;
}
long spufs_run_spu(struct spu_context *ctx, u32 *npc, u32 *event)
{
int ret;
u32 status;
if (mutex_lock_interruptible(&ctx->run_mutex))
return -ERESTARTSYS;
ctx->event_return = 0;
ret = spu_acquire(ctx);
if (ret)
goto out_unlock;
spu_enable_spu(ctx);
spu_update_sched_info(ctx);
ret = spu_run_init(ctx, npc);
if (ret) {
spu_release(ctx);
goto out;
}
do {
ret = spufs_wait(ctx->stop_wq, spu_stopped(ctx, &status));
if (unlikely(ret)) {
/*
* This is nasty: we need the state_mutex for all the
* bookkeeping even if the syscall was interrupted by
* a signal. ewww.
*/
mutex_lock(&ctx->state_mutex);
break;
}
if (unlikely(test_and_clear_bit(SPU_SCHED_NOTIFY_ACTIVE,
&ctx->sched_flags))) {
if (!(status & SPU_STATUS_STOPPED_BY_STOP))
continue;
}
spuctx_switch_state(ctx, SPU_UTIL_SYSTEM);
if ((status & SPU_STATUS_STOPPED_BY_STOP) &&
(status >> SPU_STOP_STATUS_SHIFT == 0x2104)) {
ret = spu_process_callback(ctx);
if (ret)
break;
status &= ~SPU_STATUS_STOPPED_BY_STOP;
}
ret = spufs_handle_class1(ctx);
if (ret)
break;
ret = spufs_handle_class0(ctx);
if (ret)
break;
if (signal_pending(current))
ret = -ERESTARTSYS;
} while (!ret && !(status & (SPU_STATUS_STOPPED_BY_STOP |
SPU_STATUS_STOPPED_BY_HALT |
SPU_STATUS_SINGLE_STEP)));
spu_disable_spu(ctx);
ret = spu_run_fini(ctx, npc, &status);
spu_yield(ctx);
if ((status & SPU_STATUS_STOPPED_BY_STOP) &&
(((status >> SPU_STOP_STATUS_SHIFT) & 0x3f00) == 0x2100))
ctx->stats.libassist++;
if ((ret == 0) ||
((ret == -ERESTARTSYS) &&
((status & SPU_STATUS_STOPPED_BY_HALT) ||
(status & SPU_STATUS_SINGLE_STEP) ||
((status & SPU_STATUS_STOPPED_BY_STOP) &&
(status >> SPU_STOP_STATUS_SHIFT != 0x2104)))))
ret = status;
/* Note: we don't need to force_sig SIGTRAP on single-step
* since we have TIF_SINGLESTEP set, thus the kernel will do
* it upon return from the syscall anyway.
*/
if (unlikely(status & SPU_STATUS_SINGLE_STEP))
ret = -ERESTARTSYS;
else if (unlikely((status & SPU_STATUS_STOPPED_BY_STOP)
&& (status >> SPU_STOP_STATUS_SHIFT) == 0x3fff)) {
force_sig(SIGTRAP);
ret = -ERESTARTSYS;
}
out:
*event = ctx->event_return;
out_unlock:
mutex_unlock(&ctx->run_mutex);
return ret;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* spu_restore.c
*
* (C) Copyright IBM Corp. 2005
*
* SPU-side context restore sequence outlined in
* Synergistic Processor Element Book IV
*
* Author: Mark Nutter <mnutter@us.ibm.com>
*/
#ifndef LS_SIZE
#define LS_SIZE 0x40000 /* 256K (in bytes) */
#endif
typedef unsigned int u32;
typedef unsigned long long u64;
#include <spu_intrinsics.h>
#include <asm/spu_csa.h>
#include "spu_utils.h"
#define BR_INSTR 0x327fff80 /* br -4 */
#define NOP_INSTR 0x40200000 /* nop */
#define HEQ_INSTR 0x7b000000 /* heq $0, $0 */
#define STOP_INSTR 0x00000000 /* stop 0x0 */
#define ILLEGAL_INSTR 0x00800000 /* illegal instr */
#define RESTORE_COMPLETE 0x00003ffc /* stop 0x3ffc */
static inline void fetch_regs_from_mem(addr64 lscsa_ea)
{
unsigned int ls = (unsigned int)&regs_spill[0];
unsigned int size = sizeof(regs_spill);
unsigned int tag_id = 0;
unsigned int cmd = 0x40; /* GET */
spu_writech(MFC_LSA, ls);
spu_writech(MFC_EAH, lscsa_ea.ui[0]);
spu_writech(MFC_EAL, lscsa_ea.ui[1]);
spu_writech(MFC_Size, size);
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void restore_upper_240kb(addr64 lscsa_ea)
{
unsigned int ls = 16384;
unsigned int list = (unsigned int)&dma_list[0];
unsigned int size = sizeof(dma_list);
unsigned int tag_id = 0;
unsigned int cmd = 0x44; /* GETL */
/* Restore, Step 4:
* Enqueue the GETL command (tag 0) to the MFC SPU command
* queue to transfer the upper 240 kb of LS from CSA.
*/
spu_writech(MFC_LSA, ls);
spu_writech(MFC_EAH, lscsa_ea.ui[0]);
spu_writech(MFC_EAL, list);
spu_writech(MFC_Size, size);
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void restore_decr(void)
{
unsigned int offset;
unsigned int decr_running;
unsigned int decr;
/* Restore, Step 6(moved):
* If the LSCSA "decrementer running" flag is set
* then write the SPU_WrDec channel with the
* decrementer value from LSCSA.
*/
offset = LSCSA_QW_OFFSET(decr_status);
decr_running = regs_spill[offset].slot[0] & SPU_DECR_STATUS_RUNNING;
if (decr_running) {
offset = LSCSA_QW_OFFSET(decr);
decr = regs_spill[offset].slot[0];
spu_writech(SPU_WrDec, decr);
}
}
static inline void write_ppu_mb(void)
{
unsigned int offset;
unsigned int data;
/* Restore, Step 11:
* Write the MFC_WrOut_MB channel with the PPU_MB
* data from LSCSA.
*/
offset = LSCSA_QW_OFFSET(ppu_mb);
data = regs_spill[offset].slot[0];
spu_writech(SPU_WrOutMbox, data);
}
static inline void write_ppuint_mb(void)
{
unsigned int offset;
unsigned int data;
/* Restore, Step 12:
* Write the MFC_WrInt_MB channel with the PPUINT_MB
* data from LSCSA.
*/
offset = LSCSA_QW_OFFSET(ppuint_mb);
data = regs_spill[offset].slot[0];
spu_writech(SPU_WrOutIntrMbox, data);
}
static inline void restore_fpcr(void)
{
unsigned int offset;
vector unsigned int fpcr;
/* Restore, Step 13:
* Restore the floating-point status and control
* register from the LSCSA.
*/
offset = LSCSA_QW_OFFSET(fpcr);
fpcr = regs_spill[offset].v;
spu_mtfpscr(fpcr);
}
static inline void restore_srr0(void)
{
unsigned int offset;
unsigned int srr0;
/* Restore, Step 14:
* Restore the SPU SRR0 data from the LSCSA.
*/
offset = LSCSA_QW_OFFSET(srr0);
srr0 = regs_spill[offset].slot[0];
spu_writech(SPU_WrSRR0, srr0);
}
static inline void restore_event_mask(void)
{
unsigned int offset;
unsigned int event_mask;
/* Restore, Step 15:
* Restore the SPU_RdEventMsk data from the LSCSA.
*/
offset = LSCSA_QW_OFFSET(event_mask);
event_mask = regs_spill[offset].slot[0];
spu_writech(SPU_WrEventMask, event_mask);
}
static inline void restore_tag_mask(void)
{
unsigned int offset;
unsigned int tag_mask;
/* Restore, Step 16:
* Restore the SPU_RdTagMsk data from the LSCSA.
*/
offset = LSCSA_QW_OFFSET(tag_mask);
tag_mask = regs_spill[offset].slot[0];
spu_writech(MFC_WrTagMask, tag_mask);
}
static inline void restore_complete(void)
{
extern void exit_fini(void);
unsigned int *exit_instrs = (unsigned int *)exit_fini;
unsigned int offset;
unsigned int stopped_status;
unsigned int stopped_code;
/* Restore, Step 18:
* Issue a stop-and-signal instruction with
* "good context restore" signal value.
*
* Restore, Step 19:
* There may be additional instructions placed
* here by the PPE Sequence for SPU Context
* Restore in order to restore the correct
* "stopped state".
*
* This step is handled here by analyzing the
* LSCSA.stopped_status and then modifying the
* exit() function to behave appropriately.
*/
offset = LSCSA_QW_OFFSET(stopped_status);
stopped_status = regs_spill[offset].slot[0];
stopped_code = regs_spill[offset].slot[1];
switch (stopped_status) {
case SPU_STOPPED_STATUS_P_I:
/* SPU_Status[P,I]=1. Add illegal instruction
* followed by stop-and-signal instruction after
* end of restore code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = ILLEGAL_INSTR;
exit_instrs[2] = STOP_INSTR | stopped_code;
break;
case SPU_STOPPED_STATUS_P_H:
/* SPU_Status[P,H]=1. Add 'heq $0, $0' followed
* by stop-and-signal instruction after end of
* restore code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = HEQ_INSTR;
exit_instrs[2] = STOP_INSTR | stopped_code;
break;
case SPU_STOPPED_STATUS_S_P:
/* SPU_Status[S,P]=1. Add nop instruction
* followed by 'br -4' after end of restore
* code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = STOP_INSTR | stopped_code;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
case SPU_STOPPED_STATUS_S_I:
/* SPU_Status[S,I]=1. Add illegal instruction
* followed by 'br -4' after end of restore code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = ILLEGAL_INSTR;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
case SPU_STOPPED_STATUS_I:
/* SPU_Status[I]=1. Add illegal instruction followed
* by infinite loop after end of restore sequence.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = ILLEGAL_INSTR;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
case SPU_STOPPED_STATUS_S:
/* SPU_Status[S]=1. Add two 'nop' instructions. */
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = NOP_INSTR;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
case SPU_STOPPED_STATUS_H:
/* SPU_Status[H]=1. Add 'heq $0, $0' instruction
* after end of restore code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = HEQ_INSTR;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
case SPU_STOPPED_STATUS_P:
/* SPU_Status[P]=1. Add stop-and-signal instruction
* after end of restore code.
*/
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = STOP_INSTR | stopped_code;
break;
case SPU_STOPPED_STATUS_R:
/* SPU_Status[I,S,H,P,R]=0. Add infinite loop. */
exit_instrs[0] = RESTORE_COMPLETE;
exit_instrs[1] = NOP_INSTR;
exit_instrs[2] = NOP_INSTR;
exit_instrs[3] = BR_INSTR;
break;
default:
/* SPU_Status[R]=1. No additional instructions. */
break;
}
spu_sync();
}
/**
* main - entry point for SPU-side context restore.
*
* This code deviates from the documented sequence in the
* following aspects:
*
* 1. The EA for LSCSA is passed from PPE in the
* signal notification channels.
* 2. The register spill area is pulled by SPU
* into LS, rather than pushed by PPE.
* 3. All 128 registers are restored by exit().
* 4. The exit() function is modified at run
* time in order to properly restore the
* SPU_Status register.
*/
int main()
{
addr64 lscsa_ea;
lscsa_ea.ui[0] = spu_readch(SPU_RdSigNotify1);
lscsa_ea.ui[1] = spu_readch(SPU_RdSigNotify2);
fetch_regs_from_mem(lscsa_ea);
set_event_mask(); /* Step 1. */
set_tag_mask(); /* Step 2. */
build_dma_list(lscsa_ea); /* Step 3. */
restore_upper_240kb(lscsa_ea); /* Step 4. */
/* Step 5: done by 'exit'. */
enqueue_putllc(lscsa_ea); /* Step 7. */
set_tag_update(); /* Step 8. */
read_tag_status(); /* Step 9. */
restore_decr(); /* moved Step 6. */
read_llar_status(); /* Step 10. */
write_ppu_mb(); /* Step 11. */
write_ppuint_mb(); /* Step 12. */
restore_fpcr(); /* Step 13. */
restore_srr0(); /* Step 14. */
restore_event_mask(); /* Step 15. */
restore_tag_mask(); /* Step 16. */
/* Step 17. done by 'exit'. */
restore_complete(); /* Step 18. */
return 0;
}
@@ -0,0 +1,102 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* crt0_r.S: Entry function for SPU-side context restore.
*
* Copyright (C) 2005 IBM
*
* Entry and exit function for SPU-side of the context restore
* sequence. Sets up an initial stack frame, then branches to
* 'main'. On return, restores all 128 registers from the LSCSA
* and exits.
*/
#include <asm/spu_csa.h>
.data
.align 7
.globl regs_spill
regs_spill:
.space SIZEOF_SPU_SPILL_REGS, 0x0
.text
.global _start
_start:
/* Initialize the stack pointer to point to 16368
* (16kb-16). The back chain pointer is initialized
* to NULL.
*/
il $0, 0
il $SP, 16368
stqd $0, 0($SP)
/* Allocate a minimum stack frame for the called main.
* This is needed so that main has a place to save the
* link register when it calls another function.
*/
stqd $SP, -160($SP)
ai $SP, $SP, -160
/* Call the program's main function. */
brsl $0, main
.global exit
.global _exit
exit:
_exit:
/* SPU Context Restore, Step 5: Restore the remaining 112 GPRs. */
ila $3, regs_spill + 256
restore_regs:
lqr $4, restore_reg_insts
restore_reg_loop:
ai $4, $4, 4
.balignl 16, 0x40200000
restore_reg_insts: /* must be quad-word aligned. */
lqd $16, 0($3)
lqd $17, 16($3)
lqd $18, 32($3)
lqd $19, 48($3)
andi $5, $4, 0x7F
stqr $4, restore_reg_insts
ai $3, $3, 64
brnz $5, restore_reg_loop
/* SPU Context Restore Step 17: Restore the first 16 GPRs. */
lqa $0, regs_spill + 0
lqa $1, regs_spill + 16
lqa $2, regs_spill + 32
lqa $3, regs_spill + 48
lqa $4, regs_spill + 64
lqa $5, regs_spill + 80
lqa $6, regs_spill + 96
lqa $7, regs_spill + 112
lqa $8, regs_spill + 128
lqa $9, regs_spill + 144
lqa $10, regs_spill + 160
lqa $11, regs_spill + 176
lqa $12, regs_spill + 192
lqa $13, regs_spill + 208
lqa $14, regs_spill + 224
lqa $15, regs_spill + 240
/* Under normal circumstances, the 'exit' function
* terminates with 'stop SPU_RESTORE_COMPLETE',
* indicating that the SPU-side restore code has
* completed.
*
* However it is possible that instructions immediately
* following the 'stop 0x3ffc' have been modified at run
* time so as to recreate the exact SPU_Status settings
* from the application, e.g. illegal instruciton, halt,
* etc.
*/
.global exit_fini
.global _exit_fini
exit_fini:
_exit_fini:
stop SPU_RESTORE_COMPLETE
stop 0
stop 0
stop 0
/* Pad the size of this crt0.o to be multiple of 16 bytes. */
.balignl 16, 0x0
@@ -0,0 +1,935 @@
/*
* spu_restore_dump.h: Copyright (C) 2005 IBM.
* Hex-dump auto generated from spu_restore.c.
* Do not edit!
*/
static unsigned int spu_restore_code[] __attribute__((__aligned__(128))) = {
0x40800000,
0x409ff801,
0x24000080,
0x24fd8081,
0x1cd80081,
0x33001180,
0x42034003,
0x33800284,
0x1c010204,
0x40200000,
0x40200000,
0x40200000,
0x34000190,
0x34004191,
0x34008192,
0x3400c193,
0x141fc205,
0x23fffd84,
0x1c100183,
0x217ffa85,
0x3080b000,
0x3080b201,
0x3080b402,
0x3080b603,
0x3080b804,
0x3080ba05,
0x3080bc06,
0x3080be07,
0x3080c008,
0x3080c209,
0x3080c40a,
0x3080c60b,
0x3080c80c,
0x3080ca0d,
0x3080cc0e,
0x3080ce0f,
0x00003ffc,
0x00000000,
0x00000000,
0x00000000,
0x01a00182,
0x3ec00083,
0xb0a14103,
0x01a00204,
0x3ec10083,
0x4202c002,
0xb0a14203,
0x21a00802,
0x3fbf028a,
0x3f20050a,
0x3fbe0502,
0x3fe30102,
0x21a00882,
0x3f82028b,
0x3fe3058b,
0x3fbf0584,
0x3f200204,
0x3fbe0204,
0x3fe30204,
0x04000203,
0x21a00903,
0x40848002,
0x21a00982,
0x40800003,
0x21a00a03,
0x40802002,
0x21a00a82,
0x21a00083,
0x40800082,
0x21a00b02,
0x10002612,
0x42a00003,
0x42074006,
0x1800c204,
0x40a00008,
0x40800789,
0x1c010305,
0x34000302,
0x1cffc489,
0x3ec00303,
0x3ec00287,
0xb0408403,
0x24000302,
0x34000282,
0x1c020306,
0xb0408207,
0x18020204,
0x24000282,
0x217ffa09,
0x04000402,
0x21a00802,
0x3fbe0504,
0x3fe30204,
0x21a00884,
0x42074002,
0x21a00902,
0x40803c03,
0x21a00983,
0x04000485,
0x21a00a05,
0x40802202,
0x21a00a82,
0x21a00805,
0x21a00884,
0x3fbf0582,
0x3f200102,
0x3fbe0102,
0x3fe30102,
0x21a00902,
0x40804003,
0x21a00983,
0x21a00a05,
0x40805a02,
0x21a00a82,
0x40800083,
0x21a00b83,
0x01a00c02,
0x30809c03,
0x34000182,
0x14004102,
0x21002082,
0x01a00d82,
0x3080a003,
0x34000182,
0x21a00e02,
0x3080a203,
0x34000182,
0x21a00f02,
0x3080a403,
0x34000182,
0x77400100,
0x3080a603,
0x34000182,
0x21a00702,
0x3080a803,
0x34000182,
0x21a00082,
0x3080aa03,
0x34000182,
0x21a00b02,
0x4020007f,
0x3080ae02,
0x42004805,
0x3080ac04,
0x34000103,
0x34000202,
0x1cffc183,
0x3b810106,
0x0f608184,
0x42013802,
0x5c020183,
0x38810102,
0x3b810102,
0x21000e83,
0x4020007f,
0x35000100,
0x00000470,
0x000002f8,
0x00000430,
0x00000360,
0x000002f8,
0x000003c8,
0x000004a8,
0x00000298,
0x00000360,
0x00200000,
0x409ffe02,
0x30801203,
0x40800208,
0x3ec40084,
0x40800407,
0x3ac20289,
0xb060c104,
0x3ac1c284,
0x20801203,
0x38820282,
0x41004003,
0xb0408189,
0x28820282,
0x3881c282,
0xb0408304,
0x2881c282,
0x00400000,
0x40800003,
0x35000000,
0x30809e03,
0x34000182,
0x21a00382,
0x4020007f,
0x327fde00,
0x409ffe02,
0x30801203,
0x40800206,
0x3ec40084,
0x40800407,
0x40800608,
0x3ac1828a,
0x3ac20289,
0xb060c104,
0x3ac1c284,
0x20801203,
0x38818282,
0x41004003,
0xb040818a,
0x10005b0b,
0x41201003,
0x28818282,
0x3881c282,
0xb0408184,
0x41193f83,
0x60ffc003,
0x2881c282,
0x38820282,
0xb0408189,
0x28820282,
0x327fef80,
0x409ffe02,
0x30801203,
0x40800207,
0x3ec40086,
0x4120100b,
0x10005b14,
0x40800404,
0x3ac1c289,
0x40800608,
0xb060c106,
0x3ac10286,
0x3ac2028a,
0x20801203,
0x3881c282,
0x41193f83,
0x60ffc003,
0xb0408589,
0x2881c282,
0x38810282,
0xb0408586,
0x28810282,
0x38820282,
0xb040818a,
0x28820282,
0x4020007f,
0x327fe280,
0x409ffe02,
0x30801203,
0x40800207,
0x3ec40084,
0x40800408,
0x10005b14,
0x40800609,
0x3ac1c28a,
0x3ac2028b,
0xb060c104,
0x3ac24284,
0x20801203,
0x41201003,
0x3881c282,
0xb040830a,
0x2881c282,
0x38820282,
0xb040818b,
0x41193f83,
0x60ffc003,
0x28820282,
0x38824282,
0xb0408184,
0x28824282,
0x4020007f,
0x327fd580,
0x409ffe02,
0x1000658e,
0x40800206,
0x30801203,
0x40800407,
0x3ec40084,
0x40800608,
0x3ac1828a,
0x3ac20289,
0xb060c104,
0x3ac1c284,
0x20801203,
0x413d8003,
0x38818282,
0x4020007f,
0x327fd800,
0x409ffe03,
0x30801202,
0x40800207,
0x3ec40084,
0x10005b09,
0x3ac1c288,
0xb0408184,
0x4020007f,
0x4020007f,
0x20801202,
0x3881c282,
0xb0408308,
0x2881c282,
0x327fc680,
0x409ffe02,
0x1000588b,
0x40800208,
0x30801203,
0x40800407,
0x3ec40084,
0x3ac20289,
0xb060c104,
0x3ac1c284,
0x20801203,
0x413d8003,
0x38820282,
0x327fbd80,
0x00200000,
0x00000da0,
0x00000000,
0x00000000,
0x00000000,
0x00000d90,
0x00000000,
0x00000000,
0x00000000,
0x00000db0,
0x00000000,
0x00000000,
0x00000000,
0x00000dc0,
0x00000000,
0x00000000,
0x00000000,
0x00000d80,
0x00000000,
0x00000000,
0x00000000,
0x00000df0,
0x00000000,
0x00000000,
0x00000000,
0x00000de0,
0x00000000,
0x00000000,
0x00000000,
0x00000dd0,
0x00000000,
0x00000000,
0x00000000,
0x00000e04,
0x00000000,
0x00000000,
0x00000000,
0x00000e00,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
};
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* spu_save.c
*
* (C) Copyright IBM Corp. 2005
*
* SPU-side context save sequence outlined in
* Synergistic Processor Element Book IV
*
* Author: Mark Nutter <mnutter@us.ibm.com>
*/
#ifndef LS_SIZE
#define LS_SIZE 0x40000 /* 256K (in bytes) */
#endif
typedef unsigned int u32;
typedef unsigned long long u64;
#include <spu_intrinsics.h>
#include <asm/spu_csa.h>
#include "spu_utils.h"
static inline void save_event_mask(void)
{
unsigned int offset;
/* Save, Step 2:
* Read the SPU_RdEventMsk channel and save to the LSCSA.
*/
offset = LSCSA_QW_OFFSET(event_mask);
regs_spill[offset].slot[0] = spu_readch(SPU_RdEventMask);
}
static inline void save_tag_mask(void)
{
unsigned int offset;
/* Save, Step 3:
* Read the SPU_RdTagMsk channel and save to the LSCSA.
*/
offset = LSCSA_QW_OFFSET(tag_mask);
regs_spill[offset].slot[0] = spu_readch(MFC_RdTagMask);
}
static inline void save_upper_240kb(addr64 lscsa_ea)
{
unsigned int ls = 16384;
unsigned int list = (unsigned int)&dma_list[0];
unsigned int size = sizeof(dma_list);
unsigned int tag_id = 0;
unsigned int cmd = 0x24; /* PUTL */
/* Save, Step 7:
* Enqueue the PUTL command (tag 0) to the MFC SPU command
* queue to transfer the remaining 240 kb of LS to CSA.
*/
spu_writech(MFC_LSA, ls);
spu_writech(MFC_EAH, lscsa_ea.ui[0]);
spu_writech(MFC_EAL, list);
spu_writech(MFC_Size, size);
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void save_fpcr(void)
{
// vector unsigned int fpcr;
unsigned int offset;
/* Save, Step 9:
* Issue the floating-point status and control register
* read instruction, and save to the LSCSA.
*/
offset = LSCSA_QW_OFFSET(fpcr);
regs_spill[offset].v = spu_mffpscr();
}
static inline void save_decr(void)
{
unsigned int offset;
/* Save, Step 10:
* Read and save the SPU_RdDec channel data to
* the LSCSA.
*/
offset = LSCSA_QW_OFFSET(decr);
regs_spill[offset].slot[0] = spu_readch(SPU_RdDec);
}
static inline void save_srr0(void)
{
unsigned int offset;
/* Save, Step 11:
* Read and save the SPU_WSRR0 channel data to
* the LSCSA.
*/
offset = LSCSA_QW_OFFSET(srr0);
regs_spill[offset].slot[0] = spu_readch(SPU_RdSRR0);
}
static inline void spill_regs_to_mem(addr64 lscsa_ea)
{
unsigned int ls = (unsigned int)&regs_spill[0];
unsigned int size = sizeof(regs_spill);
unsigned int tag_id = 0;
unsigned int cmd = 0x20; /* PUT */
/* Save, Step 13:
* Enqueue a PUT command (tag 0) to send the LSCSA
* to the CSA.
*/
spu_writech(MFC_LSA, ls);
spu_writech(MFC_EAH, lscsa_ea.ui[0]);
spu_writech(MFC_EAL, lscsa_ea.ui[1]);
spu_writech(MFC_Size, size);
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void enqueue_sync(addr64 lscsa_ea)
{
unsigned int tag_id = 0;
unsigned int cmd = 0xCC;
/* Save, Step 14:
* Enqueue an MFC_SYNC command (tag 0).
*/
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void save_complete(void)
{
/* Save, Step 18:
* Issue a stop-and-signal instruction indicating
* "save complete". Note: This function will not
* return!!
*/
spu_stop(SPU_SAVE_COMPLETE);
}
/**
* main - entry point for SPU-side context save.
*
* This code deviates from the documented sequence as follows:
*
* 1. The EA for LSCSA is passed from PPE in the
* signal notification channels.
* 2. All 128 registers are saved by crt0.o.
*/
int main()
{
addr64 lscsa_ea;
lscsa_ea.ui[0] = spu_readch(SPU_RdSigNotify1);
lscsa_ea.ui[1] = spu_readch(SPU_RdSigNotify2);
/* Step 1: done by exit(). */
save_event_mask(); /* Step 2. */
save_tag_mask(); /* Step 3. */
set_event_mask(); /* Step 4. */
set_tag_mask(); /* Step 5. */
build_dma_list(lscsa_ea); /* Step 6. */
save_upper_240kb(lscsa_ea); /* Step 7. */
/* Step 8: done by exit(). */
save_fpcr(); /* Step 9. */
save_decr(); /* Step 10. */
save_srr0(); /* Step 11. */
enqueue_putllc(lscsa_ea); /* Step 12. */
spill_regs_to_mem(lscsa_ea); /* Step 13. */
enqueue_sync(lscsa_ea); /* Step 14. */
set_tag_update(); /* Step 15. */
read_tag_status(); /* Step 16. */
read_llar_status(); /* Step 17. */
save_complete(); /* Step 18. */
return 0;
}
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* crt0_s.S: Entry function for SPU-side context save.
*
* Copyright (C) 2005 IBM
*
* Entry function for SPU-side of the context save sequence.
* Saves all 128 GPRs, sets up an initial stack frame, then
* branches to 'main'.
*/
#include <asm/spu_csa.h>
.data
.align 7
.globl regs_spill
regs_spill:
.space SIZEOF_SPU_SPILL_REGS, 0x0
.text
.global _start
_start:
/* SPU Context Save Step 1: Save the first 16 GPRs. */
stqa $0, regs_spill + 0
stqa $1, regs_spill + 16
stqa $2, regs_spill + 32
stqa $3, regs_spill + 48
stqa $4, regs_spill + 64
stqa $5, regs_spill + 80
stqa $6, regs_spill + 96
stqa $7, regs_spill + 112
stqa $8, regs_spill + 128
stqa $9, regs_spill + 144
stqa $10, regs_spill + 160
stqa $11, regs_spill + 176
stqa $12, regs_spill + 192
stqa $13, regs_spill + 208
stqa $14, regs_spill + 224
stqa $15, regs_spill + 240
/* SPU Context Save, Step 8: Save the remaining 112 GPRs. */
ila $3, regs_spill + 256
save_regs:
lqr $4, save_reg_insts
save_reg_loop:
ai $4, $4, 4
.balignl 16, 0x40200000
save_reg_insts: /* must be quad-word aligned. */
stqd $16, 0($3)
stqd $17, 16($3)
stqd $18, 32($3)
stqd $19, 48($3)
andi $5, $4, 0x7F
stqr $4, save_reg_insts
ai $3, $3, 64
brnz $5, save_reg_loop
/* Initialize the stack pointer to point to 16368
* (16kb-16). The back chain pointer is initialized
* to NULL.
*/
il $0, 0
il $SP, 16368
stqd $0, 0($SP)
/* Allocate a minimum stack frame for the called main.
* This is needed so that main has a place to save the
* link register when it calls another function.
*/
stqd $SP, -160($SP)
ai $SP, $SP, -160
/* Call the program's main function. */
brsl $0, main
/* In this case main should not return; if it does
* there has been an error in the sequence. Execute
* stop-and-signal with code=0.
*/
.global exit
.global _exit
exit:
_exit:
stop 0x0
/* Pad the size of this crt0.o to be multiple of 16 bytes. */
.balignl 16, 0x0
@@ -0,0 +1,743 @@
/*
* spu_save_dump.h: Copyright (C) 2005 IBM.
* Hex-dump auto generated from spu_save.c.
* Do not edit!
*/
static unsigned int spu_save_code[] __attribute__((__aligned__(128))) = {
0x20805000,
0x20805201,
0x20805402,
0x20805603,
0x20805804,
0x20805a05,
0x20805c06,
0x20805e07,
0x20806008,
0x20806209,
0x2080640a,
0x2080660b,
0x2080680c,
0x20806a0d,
0x20806c0e,
0x20806e0f,
0x4201c003,
0x33800184,
0x1c010204,
0x40200000,
0x24000190,
0x24004191,
0x24008192,
0x2400c193,
0x141fc205,
0x23fffd84,
0x1c100183,
0x217ffb85,
0x40800000,
0x409ff801,
0x24000080,
0x24fd8081,
0x1cd80081,
0x33000180,
0x00000000,
0x00000000,
0x01a00182,
0x3ec00083,
0xb1c38103,
0x01a00204,
0x3ec10082,
0x4201400d,
0xb1c38202,
0x01a00583,
0x34218682,
0x3ed80684,
0xb0408184,
0x24218682,
0x01a00603,
0x00200000,
0x34214682,
0x3ed40684,
0xb0408184,
0x40800003,
0x24214682,
0x21a00083,
0x40800082,
0x21a00b02,
0x4020007f,
0x1000251e,
0x42a00002,
0x32800008,
0x4205c00c,
0x00200000,
0x40a0000b,
0x3f82070f,
0x4080020a,
0x40800709,
0x3fe3078f,
0x3fbf0783,
0x3f200183,
0x3fbe0183,
0x3fe30187,
0x18008387,
0x4205c002,
0x3ac30404,
0x1cffc489,
0x00200000,
0x18008403,
0x38830402,
0x4cffc486,
0x3ac28185,
0xb0408584,
0x28830402,
0x1c020408,
0x38828182,
0xb0408385,
0x1802c387,
0x28828182,
0x217ff886,
0x04000582,
0x32800007,
0x21a00802,
0x3fbf0705,
0x3f200285,
0x3fbe0285,
0x3fe30285,
0x21a00885,
0x04000603,
0x21a00903,
0x40803c02,
0x21a00982,
0x04000386,
0x21a00a06,
0x40801202,
0x21a00a82,
0x73000003,
0x24200683,
0x01a00404,
0x00200000,
0x34204682,
0x3ec40683,
0xb0408203,
0x24204682,
0x01a00783,
0x00200000,
0x3421c682,
0x3edc0684,
0xb0408184,
0x2421c682,
0x21a00806,
0x21a00885,
0x3fbf0784,
0x3f200204,
0x3fbe0204,
0x3fe30204,
0x21a00904,
0x40804002,
0x21a00982,
0x21a00a06,
0x40805a02,
0x21a00a82,
0x04000683,
0x21a00803,
0x21a00885,
0x21a00904,
0x40848002,
0x21a00982,
0x21a00a06,
0x40801002,
0x21a00a82,
0x21a00a06,
0x40806602,
0x00200000,
0x35800009,
0x21a00a82,
0x40800083,
0x21a00b83,
0x01a00c02,
0x01a00d83,
0x00003ffb,
0x40800003,
0x4020007f,
0x35000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
};
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* utils.h: Utilities for SPU-side of the context switch operation.
*
* (C) Copyright IBM 2005
*/
#ifndef _SPU_CONTEXT_UTILS_H_
#define _SPU_CONTEXT_UTILS_H_
/*
* 64-bit safe EA.
*/
typedef union {
unsigned long long ull;
unsigned int ui[2];
} addr64;
/*
* 128-bit register template.
*/
typedef union {
unsigned int slot[4];
vector unsigned int v;
} spu_reg128v;
/*
* DMA list structure.
*/
struct dma_list_elem {
unsigned int size;
unsigned int ea_low;
};
/*
* Declare storage for 8-byte aligned DMA list.
*/
struct dma_list_elem dma_list[15] __attribute__ ((aligned(8)));
/*
* External definition for storage
* declared in crt0.
*/
extern spu_reg128v regs_spill[NR_SPU_SPILL_REGS];
/*
* Compute LSCSA byte offset for a given field.
*/
static struct spu_lscsa *dummy = (struct spu_lscsa *)0;
#define LSCSA_BYTE_OFFSET(_field) \
((char *)(&(dummy->_field)) - (char *)(&(dummy->gprs[0].slot[0])))
#define LSCSA_QW_OFFSET(_field) (LSCSA_BYTE_OFFSET(_field) >> 4)
static inline void set_event_mask(void)
{
unsigned int event_mask = 0;
/* Save, Step 4:
* Restore, Step 1:
* Set the SPU_RdEventMsk channel to zero to mask
* all events.
*/
spu_writech(SPU_WrEventMask, event_mask);
}
static inline void set_tag_mask(void)
{
unsigned int tag_mask = 1;
/* Save, Step 5:
* Restore, Step 2:
* Set the SPU_WrTagMsk channel to '01' to unmask
* only tag group 0.
*/
spu_writech(MFC_WrTagMask, tag_mask);
}
static inline void build_dma_list(addr64 lscsa_ea)
{
unsigned int ea_low;
int i;
/* Save, Step 6:
* Restore, Step 3:
* Update the effective address for the CSA in the
* pre-canned DMA-list in local storage.
*/
ea_low = lscsa_ea.ui[1];
ea_low += LSCSA_BYTE_OFFSET(ls[16384]);
for (i = 0; i < 15; i++, ea_low += 16384) {
dma_list[i].size = 16384;
dma_list[i].ea_low = ea_low;
}
}
static inline void enqueue_putllc(addr64 lscsa_ea)
{
unsigned int ls = 0;
unsigned int size = 128;
unsigned int tag_id = 0;
unsigned int cmd = 0xB4; /* PUTLLC */
/* Save, Step 12:
* Restore, Step 7:
* Send a PUTLLC (tag 0) command to the MFC using
* an effective address in the CSA in order to
* remove any possible lock-line reservation.
*/
spu_writech(MFC_LSA, ls);
spu_writech(MFC_EAH, lscsa_ea.ui[0]);
spu_writech(MFC_EAL, lscsa_ea.ui[1]);
spu_writech(MFC_Size, size);
spu_writech(MFC_TagID, tag_id);
spu_writech(MFC_Cmd, cmd);
}
static inline void set_tag_update(void)
{
unsigned int update_any = 1;
/* Save, Step 15:
* Restore, Step 8:
* Write the MFC_TagUpdate channel with '01'.
*/
spu_writech(MFC_WrTagUpdate, update_any);
}
static inline void read_tag_status(void)
{
/* Save, Step 16:
* Restore, Step 9:
* Read the MFC_TagStat channel data.
*/
spu_readch(MFC_RdTagStat);
}
static inline void read_llar_status(void)
{
/* Save, Step 17:
* Restore, Step 10:
* Read the MFC_AtomicStat channel data.
*/
spu_readch(MFC_RdAtomicStat);
}
#endif /* _SPU_CONTEXT_UTILS_H_ */
@@ -0,0 +1,358 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* SPU file system
*
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*/
#ifndef SPUFS_H
#define SPUFS_H
#include <linux/kref.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/fs.h>
#include <linux/cpumask.h>
#include <linux/sched/signal.h>
#include <asm/spu.h>
#include <asm/spu_csa.h>
#include <asm/spu_info.h>
#define SPUFS_PS_MAP_SIZE 0x20000
#define SPUFS_MFC_MAP_SIZE 0x1000
#define SPUFS_CNTL_MAP_SIZE 0x1000
#define SPUFS_SIGNAL_MAP_SIZE PAGE_SIZE
#define SPUFS_MSS_MAP_SIZE 0x1000
/* The magic number for our file system */
enum {
SPUFS_MAGIC = 0x23c9b64e,
};
struct spu_context_ops;
struct spu_gang;
/* ctx->sched_flags */
enum {
SPU_SCHED_NOTIFY_ACTIVE,
SPU_SCHED_WAS_ACTIVE, /* was active upon spu_acquire_saved() */
SPU_SCHED_SPU_RUN, /* context is within spu_run */
};
enum {
SWITCH_LOG_BUFSIZE = 4096,
};
enum {
SWITCH_LOG_START,
SWITCH_LOG_STOP,
SWITCH_LOG_EXIT,
};
struct switch_log {
wait_queue_head_t wait;
unsigned long head;
unsigned long tail;
struct switch_log_entry {
struct timespec64 tstamp;
s32 spu_id;
u32 type;
u32 val;
u64 timebase;
} log[];
};
struct spu_context {
struct spu *spu; /* pointer to a physical SPU */
struct spu_state csa; /* SPU context save area. */
spinlock_t mmio_lock; /* protects mmio access */
struct address_space *local_store; /* local store mapping. */
struct address_space *mfc; /* 'mfc' area mappings. */
struct address_space *cntl; /* 'control' area mappings. */
struct address_space *signal1; /* 'signal1' area mappings. */
struct address_space *signal2; /* 'signal2' area mappings. */
struct address_space *mss; /* 'mss' area mappings. */
struct address_space *psmap; /* 'psmap' area mappings. */
struct mutex mapping_lock;
u64 object_id; /* user space pointer for GNU Debugger */
enum { SPU_STATE_RUNNABLE, SPU_STATE_SAVED } state;
struct mutex state_mutex;
struct mutex run_mutex;
struct mm_struct *owner;
struct kref kref;
wait_queue_head_t ibox_wq;
wait_queue_head_t wbox_wq;
wait_queue_head_t stop_wq;
wait_queue_head_t mfc_wq;
wait_queue_head_t run_wq;
u32 tagwait;
struct spu_context_ops *ops;
struct work_struct reap_work;
unsigned long flags;
unsigned long event_return;
struct list_head gang_list;
struct spu_gang *gang;
struct kref *prof_priv_kref;
void ( * prof_priv_release) (struct kref *kref);
/* owner thread */
pid_t tid;
/* scheduler fields */
struct list_head rq;
unsigned int time_slice;
unsigned long sched_flags;
cpumask_t cpus_allowed;
int policy;
int prio;
int last_ran;
/* statistics */
struct {
/* updates protected by ctx->state_mutex */
enum spu_utilization_state util_state;
unsigned long long tstamp; /* time of last state switch */
unsigned long long times[SPU_UTIL_MAX];
unsigned long long vol_ctx_switch;
unsigned long long invol_ctx_switch;
unsigned long long min_flt;
unsigned long long maj_flt;
unsigned long long hash_flt;
unsigned long long slb_flt;
unsigned long long slb_flt_base; /* # at last ctx switch */
unsigned long long class2_intr;
unsigned long long class2_intr_base; /* # at last ctx switch */
unsigned long long libassist;
} stats;
/* context switch log */
struct switch_log *switch_log;
struct list_head aff_list;
int aff_head;
int aff_offset;
};
struct spu_gang {
struct list_head list;
struct mutex mutex;
struct kref kref;
int contexts;
struct spu_context *aff_ref_ctx;
struct list_head aff_list_head;
struct mutex aff_mutex;
int aff_flags;
struct spu *aff_ref_spu;
atomic_t aff_sched_count;
int alive;
};
/* Flag bits for spu_gang aff_flags */
#define AFF_OFFSETS_SET 1
#define AFF_MERGED 2
struct mfc_dma_command {
int32_t pad; /* reserved */
uint32_t lsa; /* local storage address */
uint64_t ea; /* effective address */
uint16_t size; /* transfer size */
uint16_t tag; /* command tag */
uint16_t class; /* class ID */
uint16_t cmd; /* command opcode */
};
/* SPU context query/set operations. */
struct spu_context_ops {
int (*mbox_read) (struct spu_context * ctx, u32 * data);
u32(*mbox_stat_read) (struct spu_context * ctx);
__poll_t (*mbox_stat_poll)(struct spu_context *ctx, __poll_t events);
int (*ibox_read) (struct spu_context * ctx, u32 * data);
int (*wbox_write) (struct spu_context * ctx, u32 data);
u32(*signal1_read) (struct spu_context * ctx);
void (*signal1_write) (struct spu_context * ctx, u32 data);
u32(*signal2_read) (struct spu_context * ctx);
void (*signal2_write) (struct spu_context * ctx, u32 data);
void (*signal1_type_set) (struct spu_context * ctx, u64 val);
u64(*signal1_type_get) (struct spu_context * ctx);
void (*signal2_type_set) (struct spu_context * ctx, u64 val);
u64(*signal2_type_get) (struct spu_context * ctx);
u32(*npc_read) (struct spu_context * ctx);
void (*npc_write) (struct spu_context * ctx, u32 data);
u32(*status_read) (struct spu_context * ctx);
char*(*get_ls) (struct spu_context * ctx);
void (*privcntl_write) (struct spu_context *ctx, u64 data);
u32 (*runcntl_read) (struct spu_context * ctx);
void (*runcntl_write) (struct spu_context * ctx, u32 data);
void (*runcntl_stop) (struct spu_context * ctx);
void (*master_start) (struct spu_context * ctx);
void (*master_stop) (struct spu_context * ctx);
int (*set_mfc_query)(struct spu_context * ctx, u32 mask, u32 mode);
u32 (*read_mfc_tagstatus)(struct spu_context * ctx);
u32 (*get_mfc_free_elements)(struct spu_context *ctx);
int (*send_mfc_command)(struct spu_context * ctx,
struct mfc_dma_command * cmd);
void (*dma_info_read) (struct spu_context * ctx,
struct spu_dma_info * info);
void (*proxydma_info_read) (struct spu_context * ctx,
struct spu_proxydma_info * info);
void (*restart_dma)(struct spu_context *ctx);
};
extern struct spu_context_ops spu_hw_ops;
extern struct spu_context_ops spu_backing_ops;
struct spufs_inode_info {
struct spu_context *i_ctx;
struct spu_gang *i_gang;
struct inode vfs_inode;
int i_openers;
};
#define SPUFS_I(inode) \
container_of(inode, struct spufs_inode_info, vfs_inode)
struct spufs_tree_descr {
const char *name;
const struct file_operations *ops;
umode_t mode;
size_t size;
};
extern const struct spufs_tree_descr spufs_dir_contents[];
extern const struct spufs_tree_descr spufs_dir_nosched_contents[];
extern const struct spufs_tree_descr spufs_dir_debug_contents[];
/* system call implementation */
extern struct spufs_calls spufs_calls;
struct coredump_params;
long spufs_run_spu(struct spu_context *ctx, u32 *npc, u32 *status);
long spufs_create(const struct path *nd, struct dentry *dentry, unsigned int flags,
umode_t mode, struct file *filp);
/* ELF coredump callbacks for writing SPU ELF notes */
extern int spufs_coredump_extra_notes_size(void);
extern int spufs_coredump_extra_notes_write(struct coredump_params *cprm);
extern const struct file_operations spufs_context_fops;
/* gang management */
struct spu_gang *alloc_spu_gang(void);
struct spu_gang *get_spu_gang(struct spu_gang *gang);
int put_spu_gang(struct spu_gang *gang);
void spu_gang_remove_ctx(struct spu_gang *gang, struct spu_context *ctx);
void spu_gang_add_ctx(struct spu_gang *gang, struct spu_context *ctx);
/* fault handling */
int spufs_handle_class1(struct spu_context *ctx);
int spufs_handle_class0(struct spu_context *ctx);
/* affinity */
struct spu *affinity_check(struct spu_context *ctx);
/* context management */
extern atomic_t nr_spu_contexts;
static inline int __must_check spu_acquire(struct spu_context *ctx)
{
return mutex_lock_interruptible(&ctx->state_mutex);
}
static inline void spu_release(struct spu_context *ctx)
{
mutex_unlock(&ctx->state_mutex);
}
struct spu_context * alloc_spu_context(struct spu_gang *gang);
void destroy_spu_context(struct kref *kref);
struct spu_context * get_spu_context(struct spu_context *ctx);
int put_spu_context(struct spu_context *ctx);
void spu_unmap_mappings(struct spu_context *ctx);
void spu_forget(struct spu_context *ctx);
int __must_check spu_acquire_saved(struct spu_context *ctx);
void spu_release_saved(struct spu_context *ctx);
int spu_stopped(struct spu_context *ctx, u32 * stat);
void spu_del_from_rq(struct spu_context *ctx);
int spu_activate(struct spu_context *ctx, unsigned long flags);
void spu_deactivate(struct spu_context *ctx);
void spu_yield(struct spu_context *ctx);
void spu_switch_log_notify(struct spu *spu, struct spu_context *ctx,
u32 type, u32 val);
void spu_set_timeslice(struct spu_context *ctx);
void spu_update_sched_info(struct spu_context *ctx);
void __spu_update_sched_info(struct spu_context *ctx);
int __init spu_sched_init(void);
void spu_sched_exit(void);
extern char *isolated_loader;
/*
* spufs_wait
* Same as wait_event_interruptible(), except that here
* we need to call spu_release(ctx) before sleeping, and
* then spu_acquire(ctx) when awoken.
*
* Returns with state_mutex re-acquired when successful or
* with -ERESTARTSYS and the state_mutex dropped when interrupted.
*/
#define spufs_wait(wq, condition) \
({ \
int __ret = 0; \
DEFINE_WAIT(__wait); \
for (;;) { \
prepare_to_wait(&(wq), &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
spu_release(ctx); \
if (signal_pending(current)) { \
__ret = -ERESTARTSYS; \
break; \
} \
schedule(); \
__ret = spu_acquire(ctx); \
if (__ret) \
break; \
} \
finish_wait(&(wq), &__wait); \
__ret; \
})
size_t spu_wbox_write(struct spu_context *ctx, u32 data);
size_t spu_ibox_read(struct spu_context *ctx, u32 *data);
/* irq callback funcs. */
void spufs_ibox_callback(struct spu *spu);
void spufs_wbox_callback(struct spu *spu);
void spufs_stop_callback(struct spu *spu, int irq);
void spufs_mfc_callback(struct spu *spu);
void spufs_dma_callback(struct spu *spu, int type);
struct spufs_coredump_reader {
char *name;
ssize_t (*dump)(struct spu_context *ctx, struct coredump_params *cprm);
u64 (*get)(struct spu_context *ctx);
size_t size;
};
extern const struct spufs_coredump_reader spufs_coredump_read[];
extern int spu_init_csa(struct spu_state *csa);
extern void spu_fini_csa(struct spu_state *csa);
extern int spu_save(struct spu_state *prev, struct spu *spu);
extern int spu_restore(struct spu_state *new, struct spu *spu);
extern int spu_switch(struct spu_state *prev, struct spu_state *new,
struct spu *spu);
extern int spu_alloc_lscsa(struct spu_state *csa);
extern void spu_free_lscsa(struct spu_state *csa);
extern void spuctx_switch_state(struct spu_context *ctx,
enum spu_utilization_state new_state);
#endif
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
#if !defined(_TRACE_SPUFS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SPUFS_H
#include <linux/tracepoint.h>
#include <linux/stringify.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM spufs
TRACE_EVENT(spufs_context,
TP_PROTO(struct spu_context *ctx, struct spu *spu, const char *name),
TP_ARGS(ctx, spu, name),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, owner_tid)
__field(int, number)
),
TP_fast_assign(
__entry->name = name;
__entry->owner_tid = ctx->tid;
__entry->number = spu ? spu->number : -1;
),
TP_printk("%s (ctxthread = %d, spu = %d)",
__entry->name, __entry->owner_tid, __entry->number)
);
#define spu_context_trace(name, ctx, spu) \
trace_spufs_context(ctx, spu, __stringify(name))
#define spu_context_nospu_trace(name, ctx) \
trace_spufs_context(ctx, NULL, __stringify(name))
#endif /* _TRACE_SPUFS_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE sputrace
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "spufs.h"
/**
* sys_spu_run - run code loaded into an SPU
*
* @unpc: next program counter for the SPU
* @ustatus: status of the SPU
*
* This system call transfers the control of execution of a
* user space thread to an SPU. It will return when the
* SPU has finished executing or when it hits an error
* condition and it will be interrupted if a signal needs
* to be delivered to a handler in user space.
*
* The next program counter is set to the passed value
* before the SPU starts fetching code and the user space
* pointer gets updated with the new value when returning
* from kernel space.
*
* The status value returned from spu_run reflects the
* value of the spu_status register after the SPU has stopped.
*
*/
static long do_spu_run(struct file *filp,
__u32 __user *unpc,
__u32 __user *ustatus)
{
long ret;
struct spufs_inode_info *i;
u32 npc, status;
ret = -EFAULT;
if (get_user(npc, unpc))
goto out;
/* check if this file was created by spu_create */
ret = -EINVAL;
if (filp->f_op != &spufs_context_fops)
goto out;
i = SPUFS_I(file_inode(filp));
ret = spufs_run_spu(i->i_ctx, &npc, &status);
if (put_user(npc, unpc))
ret = -EFAULT;
if (ustatus && put_user(status, ustatus))
ret = -EFAULT;
out:
return ret;
}
static long do_spu_create(const char __user *pathname, unsigned int flags,
umode_t mode, struct file *neighbor)
{
struct path path;
struct dentry *dentry;
int ret;
dentry = start_creating_user_path(AT_FDCWD, pathname, &path, LOOKUP_DIRECTORY);
ret = PTR_ERR(dentry);
if (!IS_ERR(dentry)) {
ret = spufs_create(&path, dentry, flags, mode, neighbor);
end_creating_path(&path, dentry);
}
return ret;
}
struct spufs_calls spufs_calls = {
.create_thread = do_spu_create,
.spu_run = do_spu_run,
.notify_spus_active = do_notify_spus_active,
.owner = THIS_MODULE,
#ifdef CONFIG_COREDUMP
.coredump_extra_notes_size = spufs_coredump_extra_notes_size,
.coredump_extra_notes_write = spufs_coredump_extra_notes_write,
#endif
};