#define pr_fmt(fmt) "vas: " fmt
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/sched/signal.h>
#include <linux/mmu_context.h>
#include <asm/icswx.h>
#include "vas.h"
#define VAS_FAULT_WIN_FIFO_SIZE (4 << 20)
static void dump_fifo(struct vas_instance *vinst, void *entry)
{
unsigned long *end = vinst->fault_fifo + vinst->fault_fifo_size;
unsigned long *fifo = entry;
int i;
pr_err("Fault fifo size %d, Max crbs %d\n", vinst->fault_fifo_size,
vinst->fault_fifo_size / CRB_SIZE);
pr_err("Fault FIFO Dump:\n");
for (i = 0; i < 10*(CRB_SIZE/8) && fifo < end; i += 4, fifo += 4) {
pr_err("[%.3d, %p]: 0x%.16lx 0x%.16lx 0x%.16lx 0x%.16lx\n",
i, fifo, *fifo, *(fifo+1), *(fifo+2), *(fifo+3));
}
}
irqreturn_t vas_fault_thread_fn(int irq, void *data)
{
struct vas_instance *vinst = data;
struct coprocessor_request_block *crb, *entry;
struct coprocessor_request_block buf;
struct pnv_vas_window *window;
unsigned long flags;
void *fifo;
crb = &buf;
while (true) {
spin_lock_irqsave(&vinst->fault_lock, flags);
fifo = vinst->fault_fifo + (vinst->fault_crbs * CRB_SIZE);
entry = fifo;
if ((entry->stamp.nx.pswid == cpu_to_be32(FIFO_INVALID_ENTRY))
|| (entry->ccw & cpu_to_be32(CCW0_INVALID))) {
vinst->fifo_in_progress = 0;
spin_unlock_irqrestore(&vinst->fault_lock, flags);
return IRQ_HANDLED;
}
spin_unlock_irqrestore(&vinst->fault_lock, flags);
vinst->fault_crbs++;
if (vinst->fault_crbs == (vinst->fault_fifo_size / CRB_SIZE))
vinst->fault_crbs = 0;
memcpy(crb, fifo, CRB_SIZE);
entry->stamp.nx.pswid = cpu_to_be32(FIFO_INVALID_ENTRY);
entry->ccw |= cpu_to_be32(CCW0_INVALID);
vas_return_credit(vinst->fault_win, false);
pr_devel("VAS[%d] fault_fifo %p, fifo %p, fault_crbs %d\n",
vinst->vas_id, vinst->fault_fifo, fifo,
vinst->fault_crbs);
vas_dump_crb(crb);
window = vas_pswid_to_window(vinst,
be32_to_cpu(crb->stamp.nx.pswid));
if (IS_ERR(window)) {
dump_fifo(vinst, (void *)entry);
pr_err("VAS[%d] fault_fifo %p, fifo %p, pswid 0x%x, fault_crbs %d bad CRB?\n",
vinst->vas_id, vinst->fault_fifo, fifo,
be32_to_cpu(crb->stamp.nx.pswid),
vinst->fault_crbs);
WARN_ON_ONCE(1);
} else {
if (window->user_win)
vas_update_csb(crb, &window->vas_win.task_ref);
else
WARN_ON_ONCE(!window->user_win);
vas_return_credit(window, true);
}
}
}
irqreturn_t vas_fault_handler(int irq, void *dev_id)
{
struct vas_instance *vinst = dev_id;
irqreturn_t ret = IRQ_WAKE_THREAD;
unsigned long flags;
spin_lock_irqsave(&vinst->fault_lock, flags);
if (vinst->fifo_in_progress)
ret = IRQ_HANDLED;
else
vinst->fifo_in_progress = 1;
spin_unlock_irqrestore(&vinst->fault_lock, flags);
return ret;
}
int vas_setup_fault_window(struct vas_instance *vinst)
{
struct vas_rx_win_attr attr;
struct vas_window *win;
vinst->fault_fifo_size = VAS_FAULT_WIN_FIFO_SIZE;
vinst->fault_fifo = kzalloc(vinst->fault_fifo_size, GFP_KERNEL);
if (!vinst->fault_fifo) {
pr_err("Unable to alloc %d bytes for fault_fifo\n",
vinst->fault_fifo_size);
return -ENOMEM;
}
memset(vinst->fault_fifo, FIFO_INVALID_ENTRY, vinst->fault_fifo_size);
vas_init_rx_win_attr(&attr, VAS_COP_TYPE_FAULT);
attr.rx_fifo_size = vinst->fault_fifo_size;
attr.rx_fifo = __pa(vinst->fault_fifo);
attr.wcreds_max = vinst->fault_fifo_size / CRB_SIZE;
attr.lnotify_lpid = 0;
attr.lnotify_pid = mfspr(SPRN_PID);
attr.lnotify_tid = mfspr(SPRN_PID);
win = vas_rx_win_open(vinst->vas_id, VAS_COP_TYPE_FAULT, &attr);
if (IS_ERR(win)) {
pr_err("VAS: Error %ld opening FaultWin\n", PTR_ERR(win));
kfree(vinst->fault_fifo);
return PTR_ERR(win);
}
vinst->fault_win = container_of(win, struct pnv_vas_window, vas_win);
pr_devel("VAS: Created FaultWin %d, LPID/PID/TID [%d/%d/%d]\n",
vinst->fault_win->vas_win.winid, attr.lnotify_lpid,
attr.lnotify_pid, attr.lnotify_tid);
return 0;
}