162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Low-level SPU handling
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (C) Copyright IBM Deutschland Entwicklung GmbH 2005
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Arnd Bergmann <arndb@de.ibm.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci#include <linux/sched/signal.h>
1062306a36Sopenharmony_ci#include <linux/mm.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <asm/spu.h>
1362306a36Sopenharmony_ci#include <asm/spu_csa.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "spufs.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/**
1862306a36Sopenharmony_ci * Handle an SPE event, depending on context SPU_CREATE_EVENTS_ENABLED flag.
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * If the context was created with events, we just set the return event.
2162306a36Sopenharmony_ci * Otherwise, send an appropriate signal to the process.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistatic void spufs_handle_event(struct spu_context *ctx,
2462306a36Sopenharmony_ci				unsigned long ea, int type)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	if (ctx->flags & SPU_CREATE_EVENTS_ENABLED) {
2762306a36Sopenharmony_ci		ctx->event_return |= type;
2862306a36Sopenharmony_ci		wake_up_all(&ctx->stop_wq);
2962306a36Sopenharmony_ci		return;
3062306a36Sopenharmony_ci	}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	switch (type) {
3362306a36Sopenharmony_ci	case SPE_EVENT_INVALID_DMA:
3462306a36Sopenharmony_ci		force_sig_fault(SIGBUS, BUS_OBJERR, NULL);
3562306a36Sopenharmony_ci		break;
3662306a36Sopenharmony_ci	case SPE_EVENT_SPE_DATA_STORAGE:
3762306a36Sopenharmony_ci		ctx->ops->restart_dma(ctx);
3862306a36Sopenharmony_ci		force_sig_fault(SIGSEGV, SEGV_ACCERR, (void __user *)ea);
3962306a36Sopenharmony_ci		break;
4062306a36Sopenharmony_ci	case SPE_EVENT_DMA_ALIGNMENT:
4162306a36Sopenharmony_ci		/* DAR isn't set for an alignment fault :( */
4262306a36Sopenharmony_ci		force_sig_fault(SIGBUS, BUS_ADRALN, NULL);
4362306a36Sopenharmony_ci		break;
4462306a36Sopenharmony_ci	case SPE_EVENT_SPE_ERROR:
4562306a36Sopenharmony_ci		force_sig_fault(
4662306a36Sopenharmony_ci			SIGILL, ILL_ILLOPC,
4762306a36Sopenharmony_ci			(void __user *)(unsigned long)
4862306a36Sopenharmony_ci			ctx->ops->npc_read(ctx) - 4);
4962306a36Sopenharmony_ci		break;
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ciint spufs_handle_class0(struct spu_context *ctx)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	unsigned long stat = ctx->csa.class_0_pending & CLASS0_INTR_MASK;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (likely(!stat))
5862306a36Sopenharmony_ci		return 0;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (stat & CLASS0_DMA_ALIGNMENT_INTR)
6162306a36Sopenharmony_ci		spufs_handle_event(ctx, ctx->csa.class_0_dar,
6262306a36Sopenharmony_ci			SPE_EVENT_DMA_ALIGNMENT);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (stat & CLASS0_INVALID_DMA_COMMAND_INTR)
6562306a36Sopenharmony_ci		spufs_handle_event(ctx, ctx->csa.class_0_dar,
6662306a36Sopenharmony_ci			SPE_EVENT_INVALID_DMA);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (stat & CLASS0_SPU_ERROR_INTR)
6962306a36Sopenharmony_ci		spufs_handle_event(ctx, ctx->csa.class_0_dar,
7062306a36Sopenharmony_ci			SPE_EVENT_SPE_ERROR);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	ctx->csa.class_0_pending = 0;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	return -EIO;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/*
7862306a36Sopenharmony_ci * bottom half handler for page faults, we can't do this from
7962306a36Sopenharmony_ci * interrupt context, since we might need to sleep.
8062306a36Sopenharmony_ci * we also need to give up the mutex so we can get scheduled
8162306a36Sopenharmony_ci * out while waiting for the backing store.
8262306a36Sopenharmony_ci *
8362306a36Sopenharmony_ci * TODO: try calling hash_page from the interrupt handler first
8462306a36Sopenharmony_ci *       in order to speed up the easy case.
8562306a36Sopenharmony_ci */
8662306a36Sopenharmony_ciint spufs_handle_class1(struct spu_context *ctx)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	u64 ea, dsisr, access;
8962306a36Sopenharmony_ci	unsigned long flags;
9062306a36Sopenharmony_ci	vm_fault_t flt = 0;
9162306a36Sopenharmony_ci	int ret;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/*
9462306a36Sopenharmony_ci	 * dar and dsisr get passed from the registers
9562306a36Sopenharmony_ci	 * to the spu_context, to this function, but not
9662306a36Sopenharmony_ci	 * back to the spu if it gets scheduled again.
9762306a36Sopenharmony_ci	 *
9862306a36Sopenharmony_ci	 * if we don't handle the fault for a saved context
9962306a36Sopenharmony_ci	 * in time, we can still expect to get the same fault
10062306a36Sopenharmony_ci	 * the immediately after the context restore.
10162306a36Sopenharmony_ci	 */
10262306a36Sopenharmony_ci	ea = ctx->csa.class_1_dar;
10362306a36Sopenharmony_ci	dsisr = ctx->csa.class_1_dsisr;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (!(dsisr & (MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED)))
10662306a36Sopenharmony_ci		return 0;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	spuctx_switch_state(ctx, SPU_UTIL_IOWAIT);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	pr_debug("ctx %p: ea %016llx, dsisr %016llx state %d\n", ctx, ea,
11162306a36Sopenharmony_ci		dsisr, ctx->state);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	ctx->stats.hash_flt++;
11462306a36Sopenharmony_ci	if (ctx->state == SPU_STATE_RUNNABLE)
11562306a36Sopenharmony_ci		ctx->spu->stats.hash_flt++;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* we must not hold the lock when entering copro_handle_mm_fault */
11862306a36Sopenharmony_ci	spu_release(ctx);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	access = (_PAGE_PRESENT | _PAGE_READ);
12162306a36Sopenharmony_ci	access |= (dsisr & MFC_DSISR_ACCESS_PUT) ? _PAGE_WRITE : 0UL;
12262306a36Sopenharmony_ci	local_irq_save(flags);
12362306a36Sopenharmony_ci	ret = hash_page(ea, access, 0x300, dsisr);
12462306a36Sopenharmony_ci	local_irq_restore(flags);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* hashing failed, so try the actual fault handler */
12762306a36Sopenharmony_ci	if (ret)
12862306a36Sopenharmony_ci		ret = copro_handle_mm_fault(current->mm, ea, dsisr, &flt);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/*
13162306a36Sopenharmony_ci	 * This is nasty: we need the state_mutex for all the bookkeeping even
13262306a36Sopenharmony_ci	 * if the syscall was interrupted by a signal. ewww.
13362306a36Sopenharmony_ci	 */
13462306a36Sopenharmony_ci	mutex_lock(&ctx->state_mutex);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * Clear dsisr under ctxt lock after handling the fault, so that
13862306a36Sopenharmony_ci	 * time slicing will not preempt the context while the page fault
13962306a36Sopenharmony_ci	 * handler is running. Context switch code removes mappings.
14062306a36Sopenharmony_ci	 */
14162306a36Sopenharmony_ci	ctx->csa.class_1_dar = ctx->csa.class_1_dsisr = 0;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	/*
14462306a36Sopenharmony_ci	 * If we handled the fault successfully and are in runnable
14562306a36Sopenharmony_ci	 * state, restart the DMA.
14662306a36Sopenharmony_ci	 * In case of unhandled error report the problem to user space.
14762306a36Sopenharmony_ci	 */
14862306a36Sopenharmony_ci	if (!ret) {
14962306a36Sopenharmony_ci		if (flt & VM_FAULT_MAJOR)
15062306a36Sopenharmony_ci			ctx->stats.maj_flt++;
15162306a36Sopenharmony_ci		else
15262306a36Sopenharmony_ci			ctx->stats.min_flt++;
15362306a36Sopenharmony_ci		if (ctx->state == SPU_STATE_RUNNABLE) {
15462306a36Sopenharmony_ci			if (flt & VM_FAULT_MAJOR)
15562306a36Sopenharmony_ci				ctx->spu->stats.maj_flt++;
15662306a36Sopenharmony_ci			else
15762306a36Sopenharmony_ci				ctx->spu->stats.min_flt++;
15862306a36Sopenharmony_ci		}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		if (ctx->spu)
16162306a36Sopenharmony_ci			ctx->ops->restart_dma(ctx);
16262306a36Sopenharmony_ci	} else
16362306a36Sopenharmony_ci		spufs_handle_event(ctx, ea, SPE_EVENT_SPE_DATA_STORAGE);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	spuctx_switch_state(ctx, SPU_UTIL_SYSTEM);
16662306a36Sopenharmony_ci	return ret;
16762306a36Sopenharmony_ci}
168