// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2017 Arm Ltd.
#define pr_fmt(fmt) "sdei: " fmt
#include <linux/acpi.h>
#include <linux/arm_sdei.h>
#include <linux/arm-smccc.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/compiler.h>
#include <linux/cpuhotplug.h>
#include <linux/cpu.h>
#include <linux/cpu_pm.h>
#include <linux/errno.h>
#include <linux/hardirq.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/kvm_host.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/percpu.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/ptrace.h>
#include <linux/preempt.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
/*
* The call to use to reach the firmware.
*/
static asmlinkage void (*sdei_firmware_call)(unsigned long function_id,
unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4, struct arm_smccc_res *res);
/* entry point from firmware to arch asm code */
static unsigned long sdei_entry_point;
struct sdei_event {
/* These three are protected by the sdei_list_lock */
struct list_head list;
bool reregister;
bool reenable;
u32 event_num;
u8 type;
u8 priority;
/* This pointer is handed to firmware as the event argument. */
union {
/* Shared events */
struct sdei_registered_event *registered;
/* CPU private events */
struct sdei_registered_event __percpu *private_registered;
};
};
/* Take the mutex for any API call or modification. Take the mutex first. */
static DEFINE_MUTEX(sdei_events_lock);
/* and then hold this when modifying the list */
static DEFINE_SPINLOCK(sdei_list_lock);
static LIST_HEAD(sdei_list);
/* Private events are registered/enabled via IPI passing one of these */
struct sdei_crosscall_args {
struct sdei_event *event;
atomic_t errors;
int first_error;
};
#define CROSSCALL_INIT(arg, event) (arg.event = event, \
arg.first_error = 0, \
atomic_set(&arg.errors, 0))
static inline int sdei_do_cross_call(void *fn, struct sdei_event * event)
{
struct sdei_crosscall_args arg;
CROSSCALL_INIT(arg, event);
on_each_cpu(fn, &arg, true);
return arg.first_error;
}
static inline void
sdei_cross_call_return(struct sdei_crosscall_args *arg, int err)
{
if (err && (atomic_inc_return(&arg->errors) == 1))
arg->first_error = err;
}
static int sdei_to_linux_errno(unsigned long sdei_err)
{
switch (sdei_err) {
case SDEI_NOT_SUPPORTED:
return -EOPNOTSUPP;
case SDEI_INVALID_PARAMETERS:
return -EINVAL;
case SDEI_DENIED:
return -EPERM;
case SDEI_PENDING:
return -EINPROGRESS;
case SDEI_OUT_OF_RESOURCE:
return -ENOMEM;
}
/* Not an error value ... */
return sdei_err;
}
/*
* If x0 is any of these values, then the call failed, use sdei_to_linux_errno()
* to translate.
*/
static int sdei_is_err(struct arm_smccc_res *res)
{
switch (res->a0) {
case SDEI_NOT_SUPPORTED:
case SDEI_INVALID_PARAMETERS:
case SDEI_DENIED:
case SDEI_PENDING:
case SDEI_OUT_OF_RESOURCE:
return true;
}
return false;
}
static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
u64 *result)
{
int err = 0;
struct arm_smccc_res res;
if (sdei_firmware_call) {
sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4,
&res);
if (sdei_is_err(&res))
err = sdei_to_linux_errno(res.a0);