162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// Copyright (C) 2017 Arm Ltd. 362306a36Sopenharmony_ci#define pr_fmt(fmt) "sdei: " fmt 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <acpi/ghes.h> 662306a36Sopenharmony_ci#include <linux/acpi.h> 762306a36Sopenharmony_ci#include <linux/arm_sdei.h> 862306a36Sopenharmony_ci#include <linux/arm-smccc.h> 962306a36Sopenharmony_ci#include <linux/atomic.h> 1062306a36Sopenharmony_ci#include <linux/bitops.h> 1162306a36Sopenharmony_ci#include <linux/compiler.h> 1262306a36Sopenharmony_ci#include <linux/cpuhotplug.h> 1362306a36Sopenharmony_ci#include <linux/cpu.h> 1462306a36Sopenharmony_ci#include <linux/cpu_pm.h> 1562306a36Sopenharmony_ci#include <linux/errno.h> 1662306a36Sopenharmony_ci#include <linux/hardirq.h> 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/kprobes.h> 1962306a36Sopenharmony_ci#include <linux/kvm_host.h> 2062306a36Sopenharmony_ci#include <linux/list.h> 2162306a36Sopenharmony_ci#include <linux/mutex.h> 2262306a36Sopenharmony_ci#include <linux/notifier.h> 2362306a36Sopenharmony_ci#include <linux/of.h> 2462306a36Sopenharmony_ci#include <linux/of_platform.h> 2562306a36Sopenharmony_ci#include <linux/percpu.h> 2662306a36Sopenharmony_ci#include <linux/platform_device.h> 2762306a36Sopenharmony_ci#include <linux/pm.h> 2862306a36Sopenharmony_ci#include <linux/ptrace.h> 2962306a36Sopenharmony_ci#include <linux/preempt.h> 3062306a36Sopenharmony_ci#include <linux/reboot.h> 3162306a36Sopenharmony_ci#include <linux/slab.h> 3262306a36Sopenharmony_ci#include <linux/smp.h> 3362306a36Sopenharmony_ci#include <linux/spinlock.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* 3662306a36Sopenharmony_ci * The call to use to reach the firmware. 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_cistatic asmlinkage void (*sdei_firmware_call)(unsigned long function_id, 3962306a36Sopenharmony_ci unsigned long arg0, unsigned long arg1, 4062306a36Sopenharmony_ci unsigned long arg2, unsigned long arg3, 4162306a36Sopenharmony_ci unsigned long arg4, struct arm_smccc_res *res); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* entry point from firmware to arch asm code */ 4462306a36Sopenharmony_cistatic unsigned long sdei_entry_point; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int sdei_hp_state; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct sdei_event { 4962306a36Sopenharmony_ci /* These three are protected by the sdei_list_lock */ 5062306a36Sopenharmony_ci struct list_head list; 5162306a36Sopenharmony_ci bool reregister; 5262306a36Sopenharmony_ci bool reenable; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci u32 event_num; 5562306a36Sopenharmony_ci u8 type; 5662306a36Sopenharmony_ci u8 priority; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* This pointer is handed to firmware as the event argument. */ 5962306a36Sopenharmony_ci union { 6062306a36Sopenharmony_ci /* Shared events */ 6162306a36Sopenharmony_ci struct sdei_registered_event *registered; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* CPU private events */ 6462306a36Sopenharmony_ci struct sdei_registered_event __percpu *private_registered; 6562306a36Sopenharmony_ci }; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* Take the mutex for any API call or modification. Take the mutex first. */ 6962306a36Sopenharmony_cistatic DEFINE_MUTEX(sdei_events_lock); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* and then hold this when modifying the list */ 7262306a36Sopenharmony_cistatic DEFINE_SPINLOCK(sdei_list_lock); 7362306a36Sopenharmony_cistatic LIST_HEAD(sdei_list); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* Private events are registered/enabled via IPI passing one of these */ 7662306a36Sopenharmony_cistruct sdei_crosscall_args { 7762306a36Sopenharmony_ci struct sdei_event *event; 7862306a36Sopenharmony_ci atomic_t errors; 7962306a36Sopenharmony_ci int first_error; 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci#define CROSSCALL_INIT(arg, event) \ 8362306a36Sopenharmony_ci do { \ 8462306a36Sopenharmony_ci arg.event = event; \ 8562306a36Sopenharmony_ci arg.first_error = 0; \ 8662306a36Sopenharmony_ci atomic_set(&arg.errors, 0); \ 8762306a36Sopenharmony_ci } while (0) 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic inline int sdei_do_local_call(smp_call_func_t fn, 9062306a36Sopenharmony_ci struct sdei_event *event) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct sdei_crosscall_args arg; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci CROSSCALL_INIT(arg, event); 9562306a36Sopenharmony_ci fn(&arg); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return arg.first_error; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic inline int sdei_do_cross_call(smp_call_func_t fn, 10162306a36Sopenharmony_ci struct sdei_event *event) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct sdei_crosscall_args arg; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci CROSSCALL_INIT(arg, event); 10662306a36Sopenharmony_ci on_each_cpu(fn, &arg, true); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return arg.first_error; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic inline void 11262306a36Sopenharmony_cisdei_cross_call_return(struct sdei_crosscall_args *arg, int err) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci if (err && (atomic_inc_return(&arg->errors) == 1)) 11562306a36Sopenharmony_ci arg->first_error = err; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int sdei_to_linux_errno(unsigned long sdei_err) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci switch (sdei_err) { 12162306a36Sopenharmony_ci case SDEI_NOT_SUPPORTED: 12262306a36Sopenharmony_ci return -EOPNOTSUPP; 12362306a36Sopenharmony_ci case SDEI_INVALID_PARAMETERS: 12462306a36Sopenharmony_ci return -EINVAL; 12562306a36Sopenharmony_ci case SDEI_DENIED: 12662306a36Sopenharmony_ci return -EPERM; 12762306a36Sopenharmony_ci case SDEI_PENDING: 12862306a36Sopenharmony_ci return -EINPROGRESS; 12962306a36Sopenharmony_ci case SDEI_OUT_OF_RESOURCE: 13062306a36Sopenharmony_ci return -ENOMEM; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int invoke_sdei_fn(unsigned long function_id, unsigned long arg0, 13762306a36Sopenharmony_ci unsigned long arg1, unsigned long arg2, 13862306a36Sopenharmony_ci unsigned long arg3, unsigned long arg4, 13962306a36Sopenharmony_ci u64 *result) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci int err; 14262306a36Sopenharmony_ci struct arm_smccc_res res; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (sdei_firmware_call) { 14562306a36Sopenharmony_ci sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4, 14662306a36Sopenharmony_ci &res); 14762306a36Sopenharmony_ci err = sdei_to_linux_errno(res.a0); 14862306a36Sopenharmony_ci } else { 14962306a36Sopenharmony_ci /* 15062306a36Sopenharmony_ci * !sdei_firmware_call means we failed to probe or called 15162306a36Sopenharmony_ci * sdei_mark_interface_broken(). -EIO is not an error returned 15262306a36Sopenharmony_ci * by sdei_to_linux_errno() and is used to suppress messages 15362306a36Sopenharmony_ci * from this driver. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_ci err = -EIO; 15662306a36Sopenharmony_ci res.a0 = SDEI_NOT_SUPPORTED; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (result) 16062306a36Sopenharmony_ci *result = res.a0; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return err; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ciNOKPROBE_SYMBOL(invoke_sdei_fn); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic struct sdei_event *sdei_event_find(u32 event_num) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct sdei_event *e, *found = NULL; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci lockdep_assert_held(&sdei_events_lock); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 17362306a36Sopenharmony_ci list_for_each_entry(e, &sdei_list, list) { 17462306a36Sopenharmony_ci if (e->event_num == event_num) { 17562306a36Sopenharmony_ci found = e; 17662306a36Sopenharmony_ci break; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci return found; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ciint sdei_api_event_context(u32 query, u64 *result) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0, 18762306a36Sopenharmony_ci result); 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ciNOKPROBE_SYMBOL(sdei_api_event_context); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int sdei_api_event_get_info(u32 event, u32 info, u64 *result) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, 19462306a36Sopenharmony_ci 0, 0, result); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic struct sdei_event *sdei_event_create(u32 event_num, 19862306a36Sopenharmony_ci sdei_event_callback *cb, 19962306a36Sopenharmony_ci void *cb_arg) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci int err; 20262306a36Sopenharmony_ci u64 result; 20362306a36Sopenharmony_ci struct sdei_event *event; 20462306a36Sopenharmony_ci struct sdei_registered_event *reg; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci lockdep_assert_held(&sdei_events_lock); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci event = kzalloc(sizeof(*event), GFP_KERNEL); 20962306a36Sopenharmony_ci if (!event) { 21062306a36Sopenharmony_ci err = -ENOMEM; 21162306a36Sopenharmony_ci goto fail; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci INIT_LIST_HEAD(&event->list); 21562306a36Sopenharmony_ci event->event_num = event_num; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 21862306a36Sopenharmony_ci &result); 21962306a36Sopenharmony_ci if (err) 22062306a36Sopenharmony_ci goto fail; 22162306a36Sopenharmony_ci event->priority = result; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE, 22462306a36Sopenharmony_ci &result); 22562306a36Sopenharmony_ci if (err) 22662306a36Sopenharmony_ci goto fail; 22762306a36Sopenharmony_ci event->type = result; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) { 23062306a36Sopenharmony_ci reg = kzalloc(sizeof(*reg), GFP_KERNEL); 23162306a36Sopenharmony_ci if (!reg) { 23262306a36Sopenharmony_ci err = -ENOMEM; 23362306a36Sopenharmony_ci goto fail; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci reg->event_num = event->event_num; 23762306a36Sopenharmony_ci reg->priority = event->priority; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci reg->callback = cb; 24062306a36Sopenharmony_ci reg->callback_arg = cb_arg; 24162306a36Sopenharmony_ci event->registered = reg; 24262306a36Sopenharmony_ci } else { 24362306a36Sopenharmony_ci int cpu; 24462306a36Sopenharmony_ci struct sdei_registered_event __percpu *regs; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci regs = alloc_percpu(struct sdei_registered_event); 24762306a36Sopenharmony_ci if (!regs) { 24862306a36Sopenharmony_ci err = -ENOMEM; 24962306a36Sopenharmony_ci goto fail; 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci for_each_possible_cpu(cpu) { 25362306a36Sopenharmony_ci reg = per_cpu_ptr(regs, cpu); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci reg->event_num = event->event_num; 25662306a36Sopenharmony_ci reg->priority = event->priority; 25762306a36Sopenharmony_ci reg->callback = cb; 25862306a36Sopenharmony_ci reg->callback_arg = cb_arg; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci event->private_registered = regs; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 26562306a36Sopenharmony_ci list_add(&event->list, &sdei_list); 26662306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return event; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cifail: 27162306a36Sopenharmony_ci kfree(event); 27262306a36Sopenharmony_ci return ERR_PTR(err); 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic void sdei_event_destroy_llocked(struct sdei_event *event) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci lockdep_assert_held(&sdei_events_lock); 27862306a36Sopenharmony_ci lockdep_assert_held(&sdei_list_lock); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci list_del(&event->list); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 28362306a36Sopenharmony_ci kfree(event->registered); 28462306a36Sopenharmony_ci else 28562306a36Sopenharmony_ci free_percpu(event->private_registered); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci kfree(event); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic void sdei_event_destroy(struct sdei_event *event) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 29362306a36Sopenharmony_ci sdei_event_destroy_llocked(event); 29462306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic int sdei_api_get_version(u64 *version) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ciint sdei_mask_local_cpu(void) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci int err; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL); 30762306a36Sopenharmony_ci if (err && err != -EIO) { 30862306a36Sopenharmony_ci pr_warn_once("failed to mask CPU[%u]: %d\n", 30962306a36Sopenharmony_ci smp_processor_id(), err); 31062306a36Sopenharmony_ci return err; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci return 0; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic void _ipi_mask_cpu(void *ignored) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci WARN_ON_ONCE(preemptible()); 31962306a36Sopenharmony_ci sdei_mask_local_cpu(); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ciint sdei_unmask_local_cpu(void) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci int err; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL); 32762306a36Sopenharmony_ci if (err && err != -EIO) { 32862306a36Sopenharmony_ci pr_warn_once("failed to unmask CPU[%u]: %d\n", 32962306a36Sopenharmony_ci smp_processor_id(), err); 33062306a36Sopenharmony_ci return err; 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci return 0; 33462306a36Sopenharmony_ci} 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cistatic void _ipi_unmask_cpu(void *ignored) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci WARN_ON_ONCE(preemptible()); 33962306a36Sopenharmony_ci sdei_unmask_local_cpu(); 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic void _ipi_private_reset(void *ignored) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci int err; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci WARN_ON_ONCE(preemptible()); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0, 34962306a36Sopenharmony_ci NULL); 35062306a36Sopenharmony_ci if (err && err != -EIO) 35162306a36Sopenharmony_ci pr_warn_once("failed to reset CPU[%u]: %d\n", 35262306a36Sopenharmony_ci smp_processor_id(), err); 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cistatic int sdei_api_shared_reset(void) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0, 35862306a36Sopenharmony_ci NULL); 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_cistatic void sdei_mark_interface_broken(void) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci pr_err("disabling SDEI firmware interface\n"); 36462306a36Sopenharmony_ci on_each_cpu(&_ipi_mask_cpu, NULL, true); 36562306a36Sopenharmony_ci sdei_firmware_call = NULL; 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int sdei_platform_reset(void) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci int err; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci on_each_cpu(&_ipi_private_reset, NULL, true); 37362306a36Sopenharmony_ci err = sdei_api_shared_reset(); 37462306a36Sopenharmony_ci if (err) { 37562306a36Sopenharmony_ci pr_err("Failed to reset platform: %d\n", err); 37662306a36Sopenharmony_ci sdei_mark_interface_broken(); 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return err; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cistatic int sdei_api_event_enable(u32 event_num) 38362306a36Sopenharmony_ci{ 38462306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 38562306a36Sopenharmony_ci 0, NULL); 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci/* Called directly by the hotplug callbacks */ 38962306a36Sopenharmony_cistatic void _local_event_enable(void *data) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci int err; 39262306a36Sopenharmony_ci struct sdei_crosscall_args *arg = data; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci err = sdei_api_event_enable(arg->event->event_num); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci sdei_cross_call_return(arg, err); 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ciint sdei_event_enable(u32 event_num) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci int err = -EINVAL; 40262306a36Sopenharmony_ci struct sdei_event *event; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 40562306a36Sopenharmony_ci event = sdei_event_find(event_num); 40662306a36Sopenharmony_ci if (!event) { 40762306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 40862306a36Sopenharmony_ci return -ENOENT; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci cpus_read_lock(); 41362306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 41462306a36Sopenharmony_ci err = sdei_api_event_enable(event->event_num); 41562306a36Sopenharmony_ci else 41662306a36Sopenharmony_ci err = sdei_do_cross_call(_local_event_enable, event); 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci if (!err) { 41962306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 42062306a36Sopenharmony_ci event->reenable = true; 42162306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci cpus_read_unlock(); 42462306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci return err; 42762306a36Sopenharmony_ci} 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_cistatic int sdei_api_event_disable(u32 event_num) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 43262306a36Sopenharmony_ci 0, 0, NULL); 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cistatic void _ipi_event_disable(void *data) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci int err; 43862306a36Sopenharmony_ci struct sdei_crosscall_args *arg = data; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci err = sdei_api_event_disable(arg->event->event_num); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci sdei_cross_call_return(arg, err); 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ciint sdei_event_disable(u32 event_num) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci int err = -EINVAL; 44862306a36Sopenharmony_ci struct sdei_event *event; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 45162306a36Sopenharmony_ci event = sdei_event_find(event_num); 45262306a36Sopenharmony_ci if (!event) { 45362306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 45462306a36Sopenharmony_ci return -ENOENT; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 45862306a36Sopenharmony_ci event->reenable = false; 45962306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 46262306a36Sopenharmony_ci err = sdei_api_event_disable(event->event_num); 46362306a36Sopenharmony_ci else 46462306a36Sopenharmony_ci err = sdei_do_cross_call(_ipi_event_disable, event); 46562306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci return err; 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cistatic int sdei_api_event_unregister(u32 event_num) 47162306a36Sopenharmony_ci{ 47262306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0, 47362306a36Sopenharmony_ci 0, 0, 0, NULL); 47462306a36Sopenharmony_ci} 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci/* Called directly by the hotplug callbacks */ 47762306a36Sopenharmony_cistatic void _local_event_unregister(void *data) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci int err; 48062306a36Sopenharmony_ci struct sdei_crosscall_args *arg = data; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci err = sdei_api_event_unregister(arg->event->event_num); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci sdei_cross_call_return(arg, err); 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ciint sdei_event_unregister(u32 event_num) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci int err; 49062306a36Sopenharmony_ci struct sdei_event *event; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci WARN_ON(in_nmi()); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 49562306a36Sopenharmony_ci event = sdei_event_find(event_num); 49662306a36Sopenharmony_ci if (!event) { 49762306a36Sopenharmony_ci pr_warn("Event %u not registered\n", event_num); 49862306a36Sopenharmony_ci err = -ENOENT; 49962306a36Sopenharmony_ci goto unlock; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 50362306a36Sopenharmony_ci event->reregister = false; 50462306a36Sopenharmony_ci event->reenable = false; 50562306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 50862306a36Sopenharmony_ci err = sdei_api_event_unregister(event->event_num); 50962306a36Sopenharmony_ci else 51062306a36Sopenharmony_ci err = sdei_do_cross_call(_local_event_unregister, event); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci if (err) 51362306a36Sopenharmony_ci goto unlock; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci sdei_event_destroy(event); 51662306a36Sopenharmony_ciunlock: 51762306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci return err; 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci/* 52362306a36Sopenharmony_ci * unregister events, but don't destroy them as they are re-registered by 52462306a36Sopenharmony_ci * sdei_reregister_shared(). 52562306a36Sopenharmony_ci */ 52662306a36Sopenharmony_cistatic int sdei_unregister_shared(void) 52762306a36Sopenharmony_ci{ 52862306a36Sopenharmony_ci int err = 0; 52962306a36Sopenharmony_ci struct sdei_event *event; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 53262306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 53362306a36Sopenharmony_ci list_for_each_entry(event, &sdei_list, list) { 53462306a36Sopenharmony_ci if (event->type != SDEI_EVENT_TYPE_SHARED) 53562306a36Sopenharmony_ci continue; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci err = sdei_api_event_unregister(event->event_num); 53862306a36Sopenharmony_ci if (err) 53962306a36Sopenharmony_ci break; 54062306a36Sopenharmony_ci } 54162306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 54262306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci return err; 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_cistatic int sdei_api_event_register(u32 event_num, unsigned long entry_point, 54862306a36Sopenharmony_ci void *arg, u64 flags, u64 affinity) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num, 55162306a36Sopenharmony_ci (unsigned long)entry_point, (unsigned long)arg, 55262306a36Sopenharmony_ci flags, affinity, NULL); 55362306a36Sopenharmony_ci} 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci/* Called directly by the hotplug callbacks */ 55662306a36Sopenharmony_cistatic void _local_event_register(void *data) 55762306a36Sopenharmony_ci{ 55862306a36Sopenharmony_ci int err; 55962306a36Sopenharmony_ci struct sdei_registered_event *reg; 56062306a36Sopenharmony_ci struct sdei_crosscall_args *arg = data; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci reg = per_cpu_ptr(arg->event->private_registered, smp_processor_id()); 56362306a36Sopenharmony_ci err = sdei_api_event_register(arg->event->event_num, sdei_entry_point, 56462306a36Sopenharmony_ci reg, 0, 0); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci sdei_cross_call_return(arg, err); 56762306a36Sopenharmony_ci} 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ciint sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) 57062306a36Sopenharmony_ci{ 57162306a36Sopenharmony_ci int err; 57262306a36Sopenharmony_ci struct sdei_event *event; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci WARN_ON(in_nmi()); 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 57762306a36Sopenharmony_ci if (sdei_event_find(event_num)) { 57862306a36Sopenharmony_ci pr_warn("Event %u already registered\n", event_num); 57962306a36Sopenharmony_ci err = -EBUSY; 58062306a36Sopenharmony_ci goto unlock; 58162306a36Sopenharmony_ci } 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci event = sdei_event_create(event_num, cb, arg); 58462306a36Sopenharmony_ci if (IS_ERR(event)) { 58562306a36Sopenharmony_ci err = PTR_ERR(event); 58662306a36Sopenharmony_ci pr_warn("Failed to create event %u: %d\n", event_num, err); 58762306a36Sopenharmony_ci goto unlock; 58862306a36Sopenharmony_ci } 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci cpus_read_lock(); 59162306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) { 59262306a36Sopenharmony_ci err = sdei_api_event_register(event->event_num, 59362306a36Sopenharmony_ci sdei_entry_point, 59462306a36Sopenharmony_ci event->registered, 59562306a36Sopenharmony_ci SDEI_EVENT_REGISTER_RM_ANY, 0); 59662306a36Sopenharmony_ci } else { 59762306a36Sopenharmony_ci err = sdei_do_cross_call(_local_event_register, event); 59862306a36Sopenharmony_ci if (err) 59962306a36Sopenharmony_ci sdei_do_cross_call(_local_event_unregister, event); 60062306a36Sopenharmony_ci } 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci if (err) { 60362306a36Sopenharmony_ci sdei_event_destroy(event); 60462306a36Sopenharmony_ci pr_warn("Failed to register event %u: %d\n", event_num, err); 60562306a36Sopenharmony_ci goto cpu_unlock; 60662306a36Sopenharmony_ci } 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 60962306a36Sopenharmony_ci event->reregister = true; 61062306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 61162306a36Sopenharmony_cicpu_unlock: 61262306a36Sopenharmony_ci cpus_read_unlock(); 61362306a36Sopenharmony_ciunlock: 61462306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 61562306a36Sopenharmony_ci return err; 61662306a36Sopenharmony_ci} 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_cistatic int sdei_reregister_shared(void) 61962306a36Sopenharmony_ci{ 62062306a36Sopenharmony_ci int err = 0; 62162306a36Sopenharmony_ci struct sdei_event *event; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci mutex_lock(&sdei_events_lock); 62462306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 62562306a36Sopenharmony_ci list_for_each_entry(event, &sdei_list, list) { 62662306a36Sopenharmony_ci if (event->type != SDEI_EVENT_TYPE_SHARED) 62762306a36Sopenharmony_ci continue; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci if (event->reregister) { 63062306a36Sopenharmony_ci err = sdei_api_event_register(event->event_num, 63162306a36Sopenharmony_ci sdei_entry_point, event->registered, 63262306a36Sopenharmony_ci SDEI_EVENT_REGISTER_RM_ANY, 0); 63362306a36Sopenharmony_ci if (err) { 63462306a36Sopenharmony_ci pr_err("Failed to re-register event %u\n", 63562306a36Sopenharmony_ci event->event_num); 63662306a36Sopenharmony_ci sdei_event_destroy_llocked(event); 63762306a36Sopenharmony_ci break; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci if (event->reenable) { 64262306a36Sopenharmony_ci err = sdei_api_event_enable(event->event_num); 64362306a36Sopenharmony_ci if (err) { 64462306a36Sopenharmony_ci pr_err("Failed to re-enable event %u\n", 64562306a36Sopenharmony_ci event->event_num); 64662306a36Sopenharmony_ci break; 64762306a36Sopenharmony_ci } 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci } 65062306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 65162306a36Sopenharmony_ci mutex_unlock(&sdei_events_lock); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci return err; 65462306a36Sopenharmony_ci} 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_cistatic int sdei_cpuhp_down(unsigned int cpu) 65762306a36Sopenharmony_ci{ 65862306a36Sopenharmony_ci struct sdei_event *event; 65962306a36Sopenharmony_ci int err; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci /* un-register private events */ 66262306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 66362306a36Sopenharmony_ci list_for_each_entry(event, &sdei_list, list) { 66462306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 66562306a36Sopenharmony_ci continue; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci err = sdei_do_local_call(_local_event_unregister, event); 66862306a36Sopenharmony_ci if (err) { 66962306a36Sopenharmony_ci pr_err("Failed to unregister event %u: %d\n", 67062306a36Sopenharmony_ci event->event_num, err); 67162306a36Sopenharmony_ci } 67262306a36Sopenharmony_ci } 67362306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci return sdei_mask_local_cpu(); 67662306a36Sopenharmony_ci} 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_cistatic int sdei_cpuhp_up(unsigned int cpu) 67962306a36Sopenharmony_ci{ 68062306a36Sopenharmony_ci struct sdei_event *event; 68162306a36Sopenharmony_ci int err; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci /* re-register/enable private events */ 68462306a36Sopenharmony_ci spin_lock(&sdei_list_lock); 68562306a36Sopenharmony_ci list_for_each_entry(event, &sdei_list, list) { 68662306a36Sopenharmony_ci if (event->type == SDEI_EVENT_TYPE_SHARED) 68762306a36Sopenharmony_ci continue; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci if (event->reregister) { 69062306a36Sopenharmony_ci err = sdei_do_local_call(_local_event_register, event); 69162306a36Sopenharmony_ci if (err) { 69262306a36Sopenharmony_ci pr_err("Failed to re-register event %u: %d\n", 69362306a36Sopenharmony_ci event->event_num, err); 69462306a36Sopenharmony_ci } 69562306a36Sopenharmony_ci } 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci if (event->reenable) { 69862306a36Sopenharmony_ci err = sdei_do_local_call(_local_event_enable, event); 69962306a36Sopenharmony_ci if (err) { 70062306a36Sopenharmony_ci pr_err("Failed to re-enable event %u: %d\n", 70162306a36Sopenharmony_ci event->event_num, err); 70262306a36Sopenharmony_ci } 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci spin_unlock(&sdei_list_lock); 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci return sdei_unmask_local_cpu(); 70862306a36Sopenharmony_ci} 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci/* When entering idle, mask/unmask events for this cpu */ 71162306a36Sopenharmony_cistatic int sdei_pm_notifier(struct notifier_block *nb, unsigned long action, 71262306a36Sopenharmony_ci void *data) 71362306a36Sopenharmony_ci{ 71462306a36Sopenharmony_ci int rv; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci WARN_ON_ONCE(preemptible()); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci switch (action) { 71962306a36Sopenharmony_ci case CPU_PM_ENTER: 72062306a36Sopenharmony_ci rv = sdei_mask_local_cpu(); 72162306a36Sopenharmony_ci break; 72262306a36Sopenharmony_ci case CPU_PM_EXIT: 72362306a36Sopenharmony_ci case CPU_PM_ENTER_FAILED: 72462306a36Sopenharmony_ci rv = sdei_unmask_local_cpu(); 72562306a36Sopenharmony_ci break; 72662306a36Sopenharmony_ci default: 72762306a36Sopenharmony_ci return NOTIFY_DONE; 72862306a36Sopenharmony_ci } 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci if (rv) 73162306a36Sopenharmony_ci return notifier_from_errno(rv); 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci return NOTIFY_OK; 73462306a36Sopenharmony_ci} 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_cistatic struct notifier_block sdei_pm_nb = { 73762306a36Sopenharmony_ci .notifier_call = sdei_pm_notifier, 73862306a36Sopenharmony_ci}; 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cistatic int sdei_device_suspend(struct device *dev) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci on_each_cpu(_ipi_mask_cpu, NULL, true); 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci return 0; 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_cistatic int sdei_device_resume(struct device *dev) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci on_each_cpu(_ipi_unmask_cpu, NULL, true); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci return 0; 75262306a36Sopenharmony_ci} 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci/* 75562306a36Sopenharmony_ci * We need all events to be reregistered when we resume from hibernate. 75662306a36Sopenharmony_ci * 75762306a36Sopenharmony_ci * The sequence is freeze->thaw. Reboot. freeze->restore. We unregister 75862306a36Sopenharmony_ci * events during freeze, then re-register and re-enable them during thaw 75962306a36Sopenharmony_ci * and restore. 76062306a36Sopenharmony_ci */ 76162306a36Sopenharmony_cistatic int sdei_device_freeze(struct device *dev) 76262306a36Sopenharmony_ci{ 76362306a36Sopenharmony_ci int err; 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci /* unregister private events */ 76662306a36Sopenharmony_ci cpuhp_remove_state(sdei_entry_point); 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci err = sdei_unregister_shared(); 76962306a36Sopenharmony_ci if (err) 77062306a36Sopenharmony_ci return err; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci return 0; 77362306a36Sopenharmony_ci} 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_cistatic int sdei_device_thaw(struct device *dev) 77662306a36Sopenharmony_ci{ 77762306a36Sopenharmony_ci int err; 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci /* re-register shared events */ 78062306a36Sopenharmony_ci err = sdei_reregister_shared(); 78162306a36Sopenharmony_ci if (err) { 78262306a36Sopenharmony_ci pr_warn("Failed to re-register shared events...\n"); 78362306a36Sopenharmony_ci sdei_mark_interface_broken(); 78462306a36Sopenharmony_ci return err; 78562306a36Sopenharmony_ci } 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "SDEI", 78862306a36Sopenharmony_ci &sdei_cpuhp_up, &sdei_cpuhp_down); 78962306a36Sopenharmony_ci if (err < 0) { 79062306a36Sopenharmony_ci pr_warn("Failed to re-register CPU hotplug notifier...\n"); 79162306a36Sopenharmony_ci return err; 79262306a36Sopenharmony_ci } 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci sdei_hp_state = err; 79562306a36Sopenharmony_ci return 0; 79662306a36Sopenharmony_ci} 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_cistatic int sdei_device_restore(struct device *dev) 79962306a36Sopenharmony_ci{ 80062306a36Sopenharmony_ci int err; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci err = sdei_platform_reset(); 80362306a36Sopenharmony_ci if (err) 80462306a36Sopenharmony_ci return err; 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci return sdei_device_thaw(dev); 80762306a36Sopenharmony_ci} 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_cistatic const struct dev_pm_ops sdei_pm_ops = { 81062306a36Sopenharmony_ci .suspend = sdei_device_suspend, 81162306a36Sopenharmony_ci .resume = sdei_device_resume, 81262306a36Sopenharmony_ci .freeze = sdei_device_freeze, 81362306a36Sopenharmony_ci .thaw = sdei_device_thaw, 81462306a36Sopenharmony_ci .restore = sdei_device_restore, 81562306a36Sopenharmony_ci}; 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci/* 81862306a36Sopenharmony_ci * Mask all CPUs and unregister all events on panic, reboot or kexec. 81962306a36Sopenharmony_ci */ 82062306a36Sopenharmony_cistatic int sdei_reboot_notifier(struct notifier_block *nb, unsigned long action, 82162306a36Sopenharmony_ci void *data) 82262306a36Sopenharmony_ci{ 82362306a36Sopenharmony_ci /* 82462306a36Sopenharmony_ci * We are going to reset the interface, after this there is no point 82562306a36Sopenharmony_ci * doing work when we take CPUs offline. 82662306a36Sopenharmony_ci */ 82762306a36Sopenharmony_ci cpuhp_remove_state(sdei_hp_state); 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci sdei_platform_reset(); 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci return NOTIFY_OK; 83262306a36Sopenharmony_ci} 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_cistatic struct notifier_block sdei_reboot_nb = { 83562306a36Sopenharmony_ci .notifier_call = sdei_reboot_notifier, 83662306a36Sopenharmony_ci}; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_cistatic void sdei_smccc_smc(unsigned long function_id, 83962306a36Sopenharmony_ci unsigned long arg0, unsigned long arg1, 84062306a36Sopenharmony_ci unsigned long arg2, unsigned long arg3, 84162306a36Sopenharmony_ci unsigned long arg4, struct arm_smccc_res *res) 84262306a36Sopenharmony_ci{ 84362306a36Sopenharmony_ci arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 84462306a36Sopenharmony_ci} 84562306a36Sopenharmony_ciNOKPROBE_SYMBOL(sdei_smccc_smc); 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_cistatic void sdei_smccc_hvc(unsigned long function_id, 84862306a36Sopenharmony_ci unsigned long arg0, unsigned long arg1, 84962306a36Sopenharmony_ci unsigned long arg2, unsigned long arg3, 85062306a36Sopenharmony_ci unsigned long arg4, struct arm_smccc_res *res) 85162306a36Sopenharmony_ci{ 85262306a36Sopenharmony_ci arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 85362306a36Sopenharmony_ci} 85462306a36Sopenharmony_ciNOKPROBE_SYMBOL(sdei_smccc_hvc); 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ciint sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, 85762306a36Sopenharmony_ci sdei_event_callback *critical_cb) 85862306a36Sopenharmony_ci{ 85962306a36Sopenharmony_ci int err; 86062306a36Sopenharmony_ci u64 result; 86162306a36Sopenharmony_ci u32 event_num; 86262306a36Sopenharmony_ci sdei_event_callback *cb; 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 86562306a36Sopenharmony_ci return -EOPNOTSUPP; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci event_num = ghes->generic->notify.vector; 86862306a36Sopenharmony_ci if (event_num == 0) { 86962306a36Sopenharmony_ci /* 87062306a36Sopenharmony_ci * Event 0 is reserved by the specification for 87162306a36Sopenharmony_ci * SDEI_EVENT_SIGNAL. 87262306a36Sopenharmony_ci */ 87362306a36Sopenharmony_ci return -EINVAL; 87462306a36Sopenharmony_ci } 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 87762306a36Sopenharmony_ci &result); 87862306a36Sopenharmony_ci if (err) 87962306a36Sopenharmony_ci return err; 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci if (result == SDEI_EVENT_PRIORITY_CRITICAL) 88262306a36Sopenharmony_ci cb = critical_cb; 88362306a36Sopenharmony_ci else 88462306a36Sopenharmony_ci cb = normal_cb; 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci err = sdei_event_register(event_num, cb, ghes); 88762306a36Sopenharmony_ci if (!err) 88862306a36Sopenharmony_ci err = sdei_event_enable(event_num); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci return err; 89162306a36Sopenharmony_ci} 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ciint sdei_unregister_ghes(struct ghes *ghes) 89462306a36Sopenharmony_ci{ 89562306a36Sopenharmony_ci int i; 89662306a36Sopenharmony_ci int err; 89762306a36Sopenharmony_ci u32 event_num = ghes->generic->notify.vector; 89862306a36Sopenharmony_ci 89962306a36Sopenharmony_ci might_sleep(); 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 90262306a36Sopenharmony_ci return -EOPNOTSUPP; 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci /* 90562306a36Sopenharmony_ci * The event may be running on another CPU. Disable it 90662306a36Sopenharmony_ci * to stop new events, then try to unregister a few times. 90762306a36Sopenharmony_ci */ 90862306a36Sopenharmony_ci err = sdei_event_disable(event_num); 90962306a36Sopenharmony_ci if (err) 91062306a36Sopenharmony_ci return err; 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 91362306a36Sopenharmony_ci err = sdei_event_unregister(event_num); 91462306a36Sopenharmony_ci if (err != -EINPROGRESS) 91562306a36Sopenharmony_ci break; 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci schedule(); 91862306a36Sopenharmony_ci } 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci return err; 92162306a36Sopenharmony_ci} 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_cistatic int sdei_get_conduit(struct platform_device *pdev) 92462306a36Sopenharmony_ci{ 92562306a36Sopenharmony_ci const char *method; 92662306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci sdei_firmware_call = NULL; 92962306a36Sopenharmony_ci if (np) { 93062306a36Sopenharmony_ci if (of_property_read_string(np, "method", &method)) { 93162306a36Sopenharmony_ci pr_warn("missing \"method\" property\n"); 93262306a36Sopenharmony_ci return SMCCC_CONDUIT_NONE; 93362306a36Sopenharmony_ci } 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci if (!strcmp("hvc", method)) { 93662306a36Sopenharmony_ci sdei_firmware_call = &sdei_smccc_hvc; 93762306a36Sopenharmony_ci return SMCCC_CONDUIT_HVC; 93862306a36Sopenharmony_ci } else if (!strcmp("smc", method)) { 93962306a36Sopenharmony_ci sdei_firmware_call = &sdei_smccc_smc; 94062306a36Sopenharmony_ci return SMCCC_CONDUIT_SMC; 94162306a36Sopenharmony_ci } 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci pr_warn("invalid \"method\" property: %s\n", method); 94462306a36Sopenharmony_ci } else if (!acpi_disabled) { 94562306a36Sopenharmony_ci if (acpi_psci_use_hvc()) { 94662306a36Sopenharmony_ci sdei_firmware_call = &sdei_smccc_hvc; 94762306a36Sopenharmony_ci return SMCCC_CONDUIT_HVC; 94862306a36Sopenharmony_ci } else { 94962306a36Sopenharmony_ci sdei_firmware_call = &sdei_smccc_smc; 95062306a36Sopenharmony_ci return SMCCC_CONDUIT_SMC; 95162306a36Sopenharmony_ci } 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci return SMCCC_CONDUIT_NONE; 95562306a36Sopenharmony_ci} 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_cistatic int sdei_probe(struct platform_device *pdev) 95862306a36Sopenharmony_ci{ 95962306a36Sopenharmony_ci int err; 96062306a36Sopenharmony_ci u64 ver = 0; 96162306a36Sopenharmony_ci int conduit; 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci conduit = sdei_get_conduit(pdev); 96462306a36Sopenharmony_ci if (!sdei_firmware_call) 96562306a36Sopenharmony_ci return 0; 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci err = sdei_api_get_version(&ver); 96862306a36Sopenharmony_ci if (err) { 96962306a36Sopenharmony_ci pr_err("Failed to get SDEI version: %d\n", err); 97062306a36Sopenharmony_ci sdei_mark_interface_broken(); 97162306a36Sopenharmony_ci return err; 97262306a36Sopenharmony_ci } 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_ci pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n", 97562306a36Sopenharmony_ci (int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver), 97662306a36Sopenharmony_ci (int)SDEI_VERSION_VENDOR(ver)); 97762306a36Sopenharmony_ci 97862306a36Sopenharmony_ci if (SDEI_VERSION_MAJOR(ver) != 1) { 97962306a36Sopenharmony_ci pr_warn("Conflicting SDEI version detected.\n"); 98062306a36Sopenharmony_ci sdei_mark_interface_broken(); 98162306a36Sopenharmony_ci return -EINVAL; 98262306a36Sopenharmony_ci } 98362306a36Sopenharmony_ci 98462306a36Sopenharmony_ci err = sdei_platform_reset(); 98562306a36Sopenharmony_ci if (err) 98662306a36Sopenharmony_ci return err; 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci sdei_entry_point = sdei_arch_get_entry_point(conduit); 98962306a36Sopenharmony_ci if (!sdei_entry_point) { 99062306a36Sopenharmony_ci /* Not supported due to hardware or boot configuration */ 99162306a36Sopenharmony_ci sdei_mark_interface_broken(); 99262306a36Sopenharmony_ci return 0; 99362306a36Sopenharmony_ci } 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci err = cpu_pm_register_notifier(&sdei_pm_nb); 99662306a36Sopenharmony_ci if (err) { 99762306a36Sopenharmony_ci pr_warn("Failed to register CPU PM notifier...\n"); 99862306a36Sopenharmony_ci goto error; 99962306a36Sopenharmony_ci } 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci err = register_reboot_notifier(&sdei_reboot_nb); 100262306a36Sopenharmony_ci if (err) { 100362306a36Sopenharmony_ci pr_warn("Failed to register reboot notifier...\n"); 100462306a36Sopenharmony_ci goto remove_cpupm; 100562306a36Sopenharmony_ci } 100662306a36Sopenharmony_ci 100762306a36Sopenharmony_ci err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "SDEI", 100862306a36Sopenharmony_ci &sdei_cpuhp_up, &sdei_cpuhp_down); 100962306a36Sopenharmony_ci if (err < 0) { 101062306a36Sopenharmony_ci pr_warn("Failed to register CPU hotplug notifier...\n"); 101162306a36Sopenharmony_ci goto remove_reboot; 101262306a36Sopenharmony_ci } 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_ci sdei_hp_state = err; 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci return 0; 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ciremove_reboot: 101962306a36Sopenharmony_ci unregister_reboot_notifier(&sdei_reboot_nb); 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ciremove_cpupm: 102262306a36Sopenharmony_ci cpu_pm_unregister_notifier(&sdei_pm_nb); 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_cierror: 102562306a36Sopenharmony_ci sdei_mark_interface_broken(); 102662306a36Sopenharmony_ci return err; 102762306a36Sopenharmony_ci} 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_cistatic const struct of_device_id sdei_of_match[] = { 103062306a36Sopenharmony_ci { .compatible = "arm,sdei-1.0" }, 103162306a36Sopenharmony_ci {} 103262306a36Sopenharmony_ci}; 103362306a36Sopenharmony_ci 103462306a36Sopenharmony_cistatic struct platform_driver sdei_driver = { 103562306a36Sopenharmony_ci .driver = { 103662306a36Sopenharmony_ci .name = "sdei", 103762306a36Sopenharmony_ci .pm = &sdei_pm_ops, 103862306a36Sopenharmony_ci .of_match_table = sdei_of_match, 103962306a36Sopenharmony_ci }, 104062306a36Sopenharmony_ci .probe = sdei_probe, 104162306a36Sopenharmony_ci}; 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_cistatic bool __init sdei_present_acpi(void) 104462306a36Sopenharmony_ci{ 104562306a36Sopenharmony_ci acpi_status status; 104662306a36Sopenharmony_ci struct acpi_table_header *sdei_table_header; 104762306a36Sopenharmony_ci 104862306a36Sopenharmony_ci if (acpi_disabled) 104962306a36Sopenharmony_ci return false; 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci status = acpi_get_table(ACPI_SIG_SDEI, 0, &sdei_table_header); 105262306a36Sopenharmony_ci if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { 105362306a36Sopenharmony_ci const char *msg = acpi_format_exception(status); 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci pr_info("Failed to get ACPI:SDEI table, %s\n", msg); 105662306a36Sopenharmony_ci } 105762306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 105862306a36Sopenharmony_ci return false; 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_ci acpi_put_table(sdei_table_header); 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci return true; 106362306a36Sopenharmony_ci} 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_civoid __init sdei_init(void) 106662306a36Sopenharmony_ci{ 106762306a36Sopenharmony_ci struct platform_device *pdev; 106862306a36Sopenharmony_ci int ret; 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci ret = platform_driver_register(&sdei_driver); 107162306a36Sopenharmony_ci if (ret || !sdei_present_acpi()) 107262306a36Sopenharmony_ci return; 107362306a36Sopenharmony_ci 107462306a36Sopenharmony_ci pdev = platform_device_register_simple(sdei_driver.driver.name, 107562306a36Sopenharmony_ci 0, NULL, 0); 107662306a36Sopenharmony_ci if (IS_ERR(pdev)) { 107762306a36Sopenharmony_ci ret = PTR_ERR(pdev); 107862306a36Sopenharmony_ci platform_driver_unregister(&sdei_driver); 107962306a36Sopenharmony_ci pr_info("Failed to register ACPI:SDEI platform device %d\n", 108062306a36Sopenharmony_ci ret); 108162306a36Sopenharmony_ci } 108262306a36Sopenharmony_ci} 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ciint sdei_event_handler(struct pt_regs *regs, 108562306a36Sopenharmony_ci struct sdei_registered_event *arg) 108662306a36Sopenharmony_ci{ 108762306a36Sopenharmony_ci int err; 108862306a36Sopenharmony_ci u32 event_num = arg->event_num; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci err = arg->callback(event_num, regs, arg->callback_arg); 109162306a36Sopenharmony_ci if (err) 109262306a36Sopenharmony_ci pr_err_ratelimited("event %u on CPU %u failed with error: %d\n", 109362306a36Sopenharmony_ci event_num, smp_processor_id(), err); 109462306a36Sopenharmony_ci 109562306a36Sopenharmony_ci return err; 109662306a36Sopenharmony_ci} 109762306a36Sopenharmony_ciNOKPROBE_SYMBOL(sdei_event_handler); 109862306a36Sopenharmony_ci 109962306a36Sopenharmony_civoid sdei_handler_abort(void) 110062306a36Sopenharmony_ci{ 110162306a36Sopenharmony_ci /* 110262306a36Sopenharmony_ci * If the crash happened in an SDEI event handler then we need to 110362306a36Sopenharmony_ci * finish the handler with the firmware so that we can have working 110462306a36Sopenharmony_ci * interrupts in the crash kernel. 110562306a36Sopenharmony_ci */ 110662306a36Sopenharmony_ci if (__this_cpu_read(sdei_active_critical_event)) { 110762306a36Sopenharmony_ci pr_warn("still in SDEI critical event context, attempting to finish handler.\n"); 110862306a36Sopenharmony_ci __sdei_handler_abort(); 110962306a36Sopenharmony_ci __this_cpu_write(sdei_active_critical_event, NULL); 111062306a36Sopenharmony_ci } 111162306a36Sopenharmony_ci if (__this_cpu_read(sdei_active_normal_event)) { 111262306a36Sopenharmony_ci pr_warn("still in SDEI normal event context, attempting to finish handler.\n"); 111362306a36Sopenharmony_ci __sdei_handler_abort(); 111462306a36Sopenharmony_ci __this_cpu_write(sdei_active_normal_event, NULL); 111562306a36Sopenharmony_ci } 111662306a36Sopenharmony_ci} 1117