1ad6eb31eSJames Morse // SPDX-License-Identifier: GPL-2.0 2ad6eb31eSJames Morse // Copyright (C) 2017 Arm Ltd. 3ad6eb31eSJames Morse #define pr_fmt(fmt) "sdei: " fmt 4ad6eb31eSJames Morse 5f96935d3SJames Morse #include <acpi/ghes.h> 6ad6eb31eSJames Morse #include <linux/acpi.h> 7ad6eb31eSJames Morse #include <linux/arm_sdei.h> 8ad6eb31eSJames Morse #include <linux/arm-smccc.h> 9f92b5462SJames Morse #include <linux/atomic.h> 10ad6eb31eSJames Morse #include <linux/bitops.h> 11ad6eb31eSJames Morse #include <linux/compiler.h> 12da351827SJames Morse #include <linux/cpuhotplug.h> 13f92b5462SJames Morse #include <linux/cpu.h> 14da351827SJames Morse #include <linux/cpu_pm.h> 15ad6eb31eSJames Morse #include <linux/errno.h> 16ad6eb31eSJames Morse #include <linux/hardirq.h> 17ad6eb31eSJames Morse #include <linux/kernel.h> 18ad6eb31eSJames Morse #include <linux/kprobes.h> 19ad6eb31eSJames Morse #include <linux/kvm_host.h> 20ad6eb31eSJames Morse #include <linux/list.h> 21ad6eb31eSJames Morse #include <linux/mutex.h> 22da351827SJames Morse #include <linux/notifier.h> 23ad6eb31eSJames Morse #include <linux/of.h> 24ad6eb31eSJames Morse #include <linux/of_platform.h> 25ad6eb31eSJames Morse #include <linux/percpu.h> 26ad6eb31eSJames Morse #include <linux/platform_device.h> 27da351827SJames Morse #include <linux/pm.h> 28ad6eb31eSJames Morse #include <linux/ptrace.h> 29ad6eb31eSJames Morse #include <linux/preempt.h> 30da351827SJames Morse #include <linux/reboot.h> 31ad6eb31eSJames Morse #include <linux/slab.h> 32ad6eb31eSJames Morse #include <linux/smp.h> 33ad6eb31eSJames Morse #include <linux/spinlock.h> 34ad6eb31eSJames Morse 35ad6eb31eSJames Morse /* 36ad6eb31eSJames Morse * The call to use to reach the firmware. 37ad6eb31eSJames Morse */ 38ad6eb31eSJames Morse static asmlinkage void (*sdei_firmware_call)(unsigned long function_id, 39ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 40ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 41ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res); 42ad6eb31eSJames Morse 43ad6eb31eSJames Morse /* entry point from firmware to arch asm code */ 44ad6eb31eSJames Morse static unsigned long sdei_entry_point; 45ad6eb31eSJames Morse 46d2c48b23SPierre Gondois static int sdei_hp_state; 47d2c48b23SPierre Gondois 48ad6eb31eSJames Morse struct sdei_event { 49da351827SJames Morse /* These three are protected by the sdei_list_lock */ 50ad6eb31eSJames Morse struct list_head list; 51da351827SJames Morse bool reregister; 52da351827SJames Morse bool reenable; 53da351827SJames Morse 54ad6eb31eSJames Morse u32 event_num; 55ad6eb31eSJames Morse u8 type; 56ad6eb31eSJames Morse u8 priority; 57ad6eb31eSJames Morse 58ad6eb31eSJames Morse /* This pointer is handed to firmware as the event argument. */ 59f92b5462SJames Morse union { 60f92b5462SJames Morse /* Shared events */ 61ad6eb31eSJames Morse struct sdei_registered_event *registered; 62f92b5462SJames Morse 63f92b5462SJames Morse /* CPU private events */ 64f92b5462SJames Morse struct sdei_registered_event __percpu *private_registered; 65f92b5462SJames Morse }; 66ad6eb31eSJames Morse }; 67ad6eb31eSJames Morse 68ad6eb31eSJames Morse /* Take the mutex for any API call or modification. Take the mutex first. */ 69ad6eb31eSJames Morse static DEFINE_MUTEX(sdei_events_lock); 70ad6eb31eSJames Morse 71ad6eb31eSJames Morse /* and then hold this when modifying the list */ 72ad6eb31eSJames Morse static DEFINE_SPINLOCK(sdei_list_lock); 73ad6eb31eSJames Morse static LIST_HEAD(sdei_list); 74ad6eb31eSJames Morse 75f92b5462SJames Morse /* Private events are registered/enabled via IPI passing one of these */ 76f92b5462SJames Morse struct sdei_crosscall_args { 77f92b5462SJames Morse struct sdei_event *event; 78f92b5462SJames Morse atomic_t errors; 79f92b5462SJames Morse int first_error; 80f92b5462SJames Morse }; 81f92b5462SJames Morse 82a27c04e1SGavin Shan #define CROSSCALL_INIT(arg, event) \ 83a27c04e1SGavin Shan do { \ 84a27c04e1SGavin Shan arg.event = event; \ 85a27c04e1SGavin Shan arg.first_error = 0; \ 86a27c04e1SGavin Shan atomic_set(&arg.errors, 0); \ 87a27c04e1SGavin Shan } while (0) 88f92b5462SJames Morse 89f4673625SGavin Shan static inline int sdei_do_local_call(smp_call_func_t fn, 90f4673625SGavin Shan struct sdei_event *event) 91f4673625SGavin Shan { 92f4673625SGavin Shan struct sdei_crosscall_args arg; 93f4673625SGavin Shan 94f4673625SGavin Shan CROSSCALL_INIT(arg, event); 95f4673625SGavin Shan fn(&arg); 96f4673625SGavin Shan 97f4673625SGavin Shan return arg.first_error; 98f4673625SGavin Shan } 99f4673625SGavin Shan 100a27c04e1SGavin Shan static inline int sdei_do_cross_call(smp_call_func_t fn, 101a27c04e1SGavin Shan struct sdei_event *event) 102f92b5462SJames Morse { 103f92b5462SJames Morse struct sdei_crosscall_args arg; 104f92b5462SJames Morse 105f92b5462SJames Morse CROSSCALL_INIT(arg, event); 106f92b5462SJames Morse on_each_cpu(fn, &arg, true); 107f92b5462SJames Morse 108f92b5462SJames Morse return arg.first_error; 109f92b5462SJames Morse } 110f92b5462SJames Morse 111f92b5462SJames Morse static inline void 112f92b5462SJames Morse sdei_cross_call_return(struct sdei_crosscall_args *arg, int err) 113f92b5462SJames Morse { 114f92b5462SJames Morse if (err && (atomic_inc_return(&arg->errors) == 1)) 115f92b5462SJames Morse arg->first_error = err; 116f92b5462SJames Morse } 117f92b5462SJames Morse 118ad6eb31eSJames Morse static int sdei_to_linux_errno(unsigned long sdei_err) 119ad6eb31eSJames Morse { 120ad6eb31eSJames Morse switch (sdei_err) { 121ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 122ad6eb31eSJames Morse return -EOPNOTSUPP; 123ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 124ad6eb31eSJames Morse return -EINVAL; 125ad6eb31eSJames Morse case SDEI_DENIED: 126ad6eb31eSJames Morse return -EPERM; 127ad6eb31eSJames Morse case SDEI_PENDING: 128ad6eb31eSJames Morse return -EINPROGRESS; 129ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 130ad6eb31eSJames Morse return -ENOMEM; 131ad6eb31eSJames Morse } 132ad6eb31eSJames Morse 1335735f515SGavin Shan return 0; 134ad6eb31eSJames Morse } 135ad6eb31eSJames Morse 136ad6eb31eSJames Morse static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0, 137ad6eb31eSJames Morse unsigned long arg1, unsigned long arg2, 138ad6eb31eSJames Morse unsigned long arg3, unsigned long arg4, 139ad6eb31eSJames Morse u64 *result) 140ad6eb31eSJames Morse { 1415735f515SGavin Shan int err; 142ad6eb31eSJames Morse struct arm_smccc_res res; 143ad6eb31eSJames Morse 144ad6eb31eSJames Morse if (sdei_firmware_call) { 145ad6eb31eSJames Morse sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4, 146ad6eb31eSJames Morse &res); 147ad6eb31eSJames Morse err = sdei_to_linux_errno(res.a0); 148ad6eb31eSJames Morse } else { 149ad6eb31eSJames Morse /* 150ad6eb31eSJames Morse * !sdei_firmware_call means we failed to probe or called 151ad6eb31eSJames Morse * sdei_mark_interface_broken(). -EIO is not an error returned 152ad6eb31eSJames Morse * by sdei_to_linux_errno() and is used to suppress messages 153ad6eb31eSJames Morse * from this driver. 154ad6eb31eSJames Morse */ 155ad6eb31eSJames Morse err = -EIO; 156ad6eb31eSJames Morse res.a0 = SDEI_NOT_SUPPORTED; 157ad6eb31eSJames Morse } 158ad6eb31eSJames Morse 159ad6eb31eSJames Morse if (result) 160ad6eb31eSJames Morse *result = res.a0; 161ad6eb31eSJames Morse 162ad6eb31eSJames Morse return err; 163ad6eb31eSJames Morse } 1642f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(invoke_sdei_fn); 165ad6eb31eSJames Morse 166ad6eb31eSJames Morse static struct sdei_event *sdei_event_find(u32 event_num) 167ad6eb31eSJames Morse { 168ad6eb31eSJames Morse struct sdei_event *e, *found = NULL; 169ad6eb31eSJames Morse 170ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 171ad6eb31eSJames Morse 172ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 173ad6eb31eSJames Morse list_for_each_entry(e, &sdei_list, list) { 174ad6eb31eSJames Morse if (e->event_num == event_num) { 175ad6eb31eSJames Morse found = e; 176ad6eb31eSJames Morse break; 177ad6eb31eSJames Morse } 178ad6eb31eSJames Morse } 179ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 180ad6eb31eSJames Morse 181ad6eb31eSJames Morse return found; 182ad6eb31eSJames Morse } 183ad6eb31eSJames Morse 184ad6eb31eSJames Morse int sdei_api_event_context(u32 query, u64 *result) 185ad6eb31eSJames Morse { 186ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0, 187ad6eb31eSJames Morse result); 188ad6eb31eSJames Morse } 189ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_api_event_context); 190ad6eb31eSJames Morse 191ad6eb31eSJames Morse static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) 192ad6eb31eSJames Morse { 193ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, 194ad6eb31eSJames Morse 0, 0, result); 195ad6eb31eSJames Morse } 196ad6eb31eSJames Morse 197ad6eb31eSJames Morse static struct sdei_event *sdei_event_create(u32 event_num, 198ad6eb31eSJames Morse sdei_event_callback *cb, 199ad6eb31eSJames Morse void *cb_arg) 200ad6eb31eSJames Morse { 201ad6eb31eSJames Morse int err; 202ad6eb31eSJames Morse u64 result; 203ad6eb31eSJames Morse struct sdei_event *event; 204ad6eb31eSJames Morse struct sdei_registered_event *reg; 205ad6eb31eSJames Morse 206ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 207ad6eb31eSJames Morse 208ad6eb31eSJames Morse event = kzalloc(sizeof(*event), GFP_KERNEL); 20911988424SGavin Shan if (!event) { 21011988424SGavin Shan err = -ENOMEM; 21111988424SGavin Shan goto fail; 21211988424SGavin Shan } 213ad6eb31eSJames Morse 214ad6eb31eSJames Morse INIT_LIST_HEAD(&event->list); 215ad6eb31eSJames Morse event->event_num = event_num; 216ad6eb31eSJames Morse 217ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 218ad6eb31eSJames Morse &result); 21911988424SGavin Shan if (err) 22011988424SGavin Shan goto fail; 221ad6eb31eSJames Morse event->priority = result; 222ad6eb31eSJames Morse 223ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE, 224ad6eb31eSJames Morse &result); 22511988424SGavin Shan if (err) 22611988424SGavin Shan goto fail; 227ad6eb31eSJames Morse event->type = result; 228ad6eb31eSJames Morse 229ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) { 230ad6eb31eSJames Morse reg = kzalloc(sizeof(*reg), GFP_KERNEL); 231ad6eb31eSJames Morse if (!reg) { 23211988424SGavin Shan err = -ENOMEM; 23311988424SGavin Shan goto fail; 234ad6eb31eSJames Morse } 235ad6eb31eSJames Morse 236663c0e89SGavin Shan reg->event_num = event->event_num; 237ad6eb31eSJames Morse reg->priority = event->priority; 238ad6eb31eSJames Morse 239ad6eb31eSJames Morse reg->callback = cb; 240ad6eb31eSJames Morse reg->callback_arg = cb_arg; 241ad6eb31eSJames Morse event->registered = reg; 242f92b5462SJames Morse } else { 243f92b5462SJames Morse int cpu; 244f92b5462SJames Morse struct sdei_registered_event __percpu *regs; 245f92b5462SJames Morse 246f92b5462SJames Morse regs = alloc_percpu(struct sdei_registered_event); 247f92b5462SJames Morse if (!regs) { 24811988424SGavin Shan err = -ENOMEM; 24911988424SGavin Shan goto fail; 250f92b5462SJames Morse } 251f92b5462SJames Morse 252f92b5462SJames Morse for_each_possible_cpu(cpu) { 253f92b5462SJames Morse reg = per_cpu_ptr(regs, cpu); 254f92b5462SJames Morse 255f92b5462SJames Morse reg->event_num = event->event_num; 256f92b5462SJames Morse reg->priority = event->priority; 257f92b5462SJames Morse reg->callback = cb; 258f92b5462SJames Morse reg->callback_arg = cb_arg; 259f92b5462SJames Morse } 260f92b5462SJames Morse 261f92b5462SJames Morse event->private_registered = regs; 262ad6eb31eSJames Morse } 263ad6eb31eSJames Morse 264ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 265ad6eb31eSJames Morse list_add(&event->list, &sdei_list); 266ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 267ad6eb31eSJames Morse 268ad6eb31eSJames Morse return event; 26911988424SGavin Shan 27011988424SGavin Shan fail: 27111988424SGavin Shan kfree(event); 27211988424SGavin Shan return ERR_PTR(err); 273ad6eb31eSJames Morse } 274ad6eb31eSJames Morse 275c66d52b1SLiguang Zhang static void sdei_event_destroy_llocked(struct sdei_event *event) 276ad6eb31eSJames Morse { 277ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 278c66d52b1SLiguang Zhang lockdep_assert_held(&sdei_list_lock); 279ad6eb31eSJames Morse 280ad6eb31eSJames Morse list_del(&event->list); 281ad6eb31eSJames Morse 282ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 283ad6eb31eSJames Morse kfree(event->registered); 284f92b5462SJames Morse else 285f92b5462SJames Morse free_percpu(event->private_registered); 286ad6eb31eSJames Morse 287ad6eb31eSJames Morse kfree(event); 288ad6eb31eSJames Morse } 289ad6eb31eSJames Morse 290c66d52b1SLiguang Zhang static void sdei_event_destroy(struct sdei_event *event) 291c66d52b1SLiguang Zhang { 292c66d52b1SLiguang Zhang spin_lock(&sdei_list_lock); 293c66d52b1SLiguang Zhang sdei_event_destroy_llocked(event); 294c66d52b1SLiguang Zhang spin_unlock(&sdei_list_lock); 295c66d52b1SLiguang Zhang } 296c66d52b1SLiguang Zhang 297ad6eb31eSJames Morse static int sdei_api_get_version(u64 *version) 298ad6eb31eSJames Morse { 299ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); 300ad6eb31eSJames Morse } 301ad6eb31eSJames Morse 302ad6eb31eSJames Morse int sdei_mask_local_cpu(void) 303ad6eb31eSJames Morse { 304ad6eb31eSJames Morse int err; 305ad6eb31eSJames Morse 306ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL); 307ad6eb31eSJames Morse if (err && err != -EIO) { 308ad6eb31eSJames Morse pr_warn_once("failed to mask CPU[%u]: %d\n", 309ad6eb31eSJames Morse smp_processor_id(), err); 310ad6eb31eSJames Morse return err; 311ad6eb31eSJames Morse } 312ad6eb31eSJames Morse 313ad6eb31eSJames Morse return 0; 314ad6eb31eSJames Morse } 315ad6eb31eSJames Morse 316ad6eb31eSJames Morse static void _ipi_mask_cpu(void *ignored) 317ad6eb31eSJames Morse { 318d2c48b23SPierre Gondois WARN_ON_ONCE(preemptible()); 319ad6eb31eSJames Morse sdei_mask_local_cpu(); 320ad6eb31eSJames Morse } 321ad6eb31eSJames Morse 322ad6eb31eSJames Morse int sdei_unmask_local_cpu(void) 323ad6eb31eSJames Morse { 324ad6eb31eSJames Morse int err; 325ad6eb31eSJames Morse 326ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL); 327ad6eb31eSJames Morse if (err && err != -EIO) { 328ad6eb31eSJames Morse pr_warn_once("failed to unmask CPU[%u]: %d\n", 329ad6eb31eSJames Morse smp_processor_id(), err); 330ad6eb31eSJames Morse return err; 331ad6eb31eSJames Morse } 332ad6eb31eSJames Morse 333ad6eb31eSJames Morse return 0; 334ad6eb31eSJames Morse } 335ad6eb31eSJames Morse 336ad6eb31eSJames Morse static void _ipi_unmask_cpu(void *ignored) 337ad6eb31eSJames Morse { 338d2c48b23SPierre Gondois WARN_ON_ONCE(preemptible()); 339ad6eb31eSJames Morse sdei_unmask_local_cpu(); 340ad6eb31eSJames Morse } 341ad6eb31eSJames Morse 342ad6eb31eSJames Morse static void _ipi_private_reset(void *ignored) 343ad6eb31eSJames Morse { 344ad6eb31eSJames Morse int err; 345ad6eb31eSJames Morse 346d2c48b23SPierre Gondois WARN_ON_ONCE(preemptible()); 347d2c48b23SPierre Gondois 348ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0, 349ad6eb31eSJames Morse NULL); 350ad6eb31eSJames Morse if (err && err != -EIO) 351ad6eb31eSJames Morse pr_warn_once("failed to reset CPU[%u]: %d\n", 352ad6eb31eSJames Morse smp_processor_id(), err); 353ad6eb31eSJames Morse } 354ad6eb31eSJames Morse 355ad6eb31eSJames Morse static int sdei_api_shared_reset(void) 356ad6eb31eSJames Morse { 357ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0, 358ad6eb31eSJames Morse NULL); 359ad6eb31eSJames Morse } 360ad6eb31eSJames Morse 361ad6eb31eSJames Morse static void sdei_mark_interface_broken(void) 362ad6eb31eSJames Morse { 363ad6eb31eSJames Morse pr_err("disabling SDEI firmware interface\n"); 364ad6eb31eSJames Morse on_each_cpu(&_ipi_mask_cpu, NULL, true); 365ad6eb31eSJames Morse sdei_firmware_call = NULL; 366ad6eb31eSJames Morse } 367ad6eb31eSJames Morse 368ad6eb31eSJames Morse static int sdei_platform_reset(void) 369ad6eb31eSJames Morse { 370ad6eb31eSJames Morse int err; 371ad6eb31eSJames Morse 372ad6eb31eSJames Morse on_each_cpu(&_ipi_private_reset, NULL, true); 373ad6eb31eSJames Morse err = sdei_api_shared_reset(); 374ad6eb31eSJames Morse if (err) { 375ad6eb31eSJames Morse pr_err("Failed to reset platform: %d\n", err); 376ad6eb31eSJames Morse sdei_mark_interface_broken(); 377ad6eb31eSJames Morse } 378ad6eb31eSJames Morse 379ad6eb31eSJames Morse return err; 380ad6eb31eSJames Morse } 381ad6eb31eSJames Morse 382ad6eb31eSJames Morse static int sdei_api_event_enable(u32 event_num) 383ad6eb31eSJames Morse { 384ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 385ad6eb31eSJames Morse 0, NULL); 386ad6eb31eSJames Morse } 387ad6eb31eSJames Morse 388f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 389f92b5462SJames Morse static void _local_event_enable(void *data) 390f92b5462SJames Morse { 391f92b5462SJames Morse int err; 392f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 393f92b5462SJames Morse 394f92b5462SJames Morse err = sdei_api_event_enable(arg->event->event_num); 395f92b5462SJames Morse 396f92b5462SJames Morse sdei_cross_call_return(arg, err); 397f92b5462SJames Morse } 398f92b5462SJames Morse 399ad6eb31eSJames Morse int sdei_event_enable(u32 event_num) 400ad6eb31eSJames Morse { 401ad6eb31eSJames Morse int err = -EINVAL; 402ad6eb31eSJames Morse struct sdei_event *event; 403ad6eb31eSJames Morse 404ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 405ad6eb31eSJames Morse event = sdei_event_find(event_num); 406ad6eb31eSJames Morse if (!event) { 407ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 408ad6eb31eSJames Morse return -ENOENT; 409ad6eb31eSJames Morse } 410ad6eb31eSJames Morse 411da351827SJames Morse 41254f529a6SJames Morse cpus_read_lock(); 413ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 414ad6eb31eSJames Morse err = sdei_api_event_enable(event->event_num); 415f92b5462SJames Morse else 416f92b5462SJames Morse err = sdei_do_cross_call(_local_event_enable, event); 41754f529a6SJames Morse 41854f529a6SJames Morse if (!err) { 41954f529a6SJames Morse spin_lock(&sdei_list_lock); 42054f529a6SJames Morse event->reenable = true; 42154f529a6SJames Morse spin_unlock(&sdei_list_lock); 42254f529a6SJames Morse } 42354f529a6SJames Morse cpus_read_unlock(); 424ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 425ad6eb31eSJames Morse 426ad6eb31eSJames Morse return err; 427ad6eb31eSJames Morse } 428ad6eb31eSJames Morse 429ad6eb31eSJames Morse static int sdei_api_event_disable(u32 event_num) 430ad6eb31eSJames Morse { 431ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 432ad6eb31eSJames Morse 0, 0, NULL); 433ad6eb31eSJames Morse } 434ad6eb31eSJames Morse 435f92b5462SJames Morse static void _ipi_event_disable(void *data) 436f92b5462SJames Morse { 437f92b5462SJames Morse int err; 438f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 439f92b5462SJames Morse 440f92b5462SJames Morse err = sdei_api_event_disable(arg->event->event_num); 441f92b5462SJames Morse 442f92b5462SJames Morse sdei_cross_call_return(arg, err); 443f92b5462SJames Morse } 444f92b5462SJames Morse 445ad6eb31eSJames Morse int sdei_event_disable(u32 event_num) 446ad6eb31eSJames Morse { 447ad6eb31eSJames Morse int err = -EINVAL; 448ad6eb31eSJames Morse struct sdei_event *event; 449ad6eb31eSJames Morse 450ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 451ad6eb31eSJames Morse event = sdei_event_find(event_num); 452ad6eb31eSJames Morse if (!event) { 453ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 454ad6eb31eSJames Morse return -ENOENT; 455ad6eb31eSJames Morse } 456ad6eb31eSJames Morse 457da351827SJames Morse spin_lock(&sdei_list_lock); 458da351827SJames Morse event->reenable = false; 459da351827SJames Morse spin_unlock(&sdei_list_lock); 460da351827SJames Morse 461ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 462ad6eb31eSJames Morse err = sdei_api_event_disable(event->event_num); 463f92b5462SJames Morse else 464f92b5462SJames Morse err = sdei_do_cross_call(_ipi_event_disable, event); 465ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 466ad6eb31eSJames Morse 467ad6eb31eSJames Morse return err; 468ad6eb31eSJames Morse } 469ad6eb31eSJames Morse 470ad6eb31eSJames Morse static int sdei_api_event_unregister(u32 event_num) 471ad6eb31eSJames Morse { 472ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0, 473ad6eb31eSJames Morse 0, 0, 0, NULL); 474ad6eb31eSJames Morse } 475ad6eb31eSJames Morse 476f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 477f92b5462SJames Morse static void _local_event_unregister(void *data) 478f92b5462SJames Morse { 479f92b5462SJames Morse int err; 480f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 481f92b5462SJames Morse 482f92b5462SJames Morse err = sdei_api_event_unregister(arg->event->event_num); 483f92b5462SJames Morse 484f92b5462SJames Morse sdei_cross_call_return(arg, err); 485f92b5462SJames Morse } 486f92b5462SJames Morse 487ad6eb31eSJames Morse int sdei_event_unregister(u32 event_num) 488ad6eb31eSJames Morse { 489ad6eb31eSJames Morse int err; 490ad6eb31eSJames Morse struct sdei_event *event; 491ad6eb31eSJames Morse 492ad6eb31eSJames Morse WARN_ON(in_nmi()); 493ad6eb31eSJames Morse 494ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 495ad6eb31eSJames Morse event = sdei_event_find(event_num); 496ad6eb31eSJames Morse if (!event) { 497ad6eb31eSJames Morse pr_warn("Event %u not registered\n", event_num); 498ad6eb31eSJames Morse err = -ENOENT; 499b06146b6SGavin Shan goto unlock; 500ad6eb31eSJames Morse } 501ad6eb31eSJames Morse 5026ded0b61SJames Morse spin_lock(&sdei_list_lock); 5036ded0b61SJames Morse event->reregister = false; 5046ded0b61SJames Morse event->reenable = false; 5056ded0b61SJames Morse spin_unlock(&sdei_list_lock); 5066ded0b61SJames Morse 5074b2b76cbSGavin Shan if (event->type == SDEI_EVENT_TYPE_SHARED) 5084b2b76cbSGavin Shan err = sdei_api_event_unregister(event->event_num); 5094b2b76cbSGavin Shan else 5104b2b76cbSGavin Shan err = sdei_do_cross_call(_local_event_unregister, event); 5114b2b76cbSGavin Shan 512ad6eb31eSJames Morse if (err) 513b06146b6SGavin Shan goto unlock; 514ad6eb31eSJames Morse 515ad6eb31eSJames Morse sdei_event_destroy(event); 516b06146b6SGavin Shan unlock: 517ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 518ad6eb31eSJames Morse 519ad6eb31eSJames Morse return err; 520ad6eb31eSJames Morse } 521ad6eb31eSJames Morse 522da351827SJames Morse /* 523da351827SJames Morse * unregister events, but don't destroy them as they are re-registered by 524da351827SJames Morse * sdei_reregister_shared(). 525da351827SJames Morse */ 526da351827SJames Morse static int sdei_unregister_shared(void) 527da351827SJames Morse { 528da351827SJames Morse int err = 0; 529da351827SJames Morse struct sdei_event *event; 530da351827SJames Morse 531da351827SJames Morse mutex_lock(&sdei_events_lock); 532da351827SJames Morse spin_lock(&sdei_list_lock); 533da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 534da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 535da351827SJames Morse continue; 536da351827SJames Morse 5374b2b76cbSGavin Shan err = sdei_api_event_unregister(event->event_num); 538da351827SJames Morse if (err) 539da351827SJames Morse break; 540da351827SJames Morse } 541da351827SJames Morse spin_unlock(&sdei_list_lock); 542da351827SJames Morse mutex_unlock(&sdei_events_lock); 543da351827SJames Morse 544da351827SJames Morse return err; 545da351827SJames Morse } 546da351827SJames Morse 547ad6eb31eSJames Morse static int sdei_api_event_register(u32 event_num, unsigned long entry_point, 548ad6eb31eSJames Morse void *arg, u64 flags, u64 affinity) 549ad6eb31eSJames Morse { 550ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num, 551ad6eb31eSJames Morse (unsigned long)entry_point, (unsigned long)arg, 552ad6eb31eSJames Morse flags, affinity, NULL); 553ad6eb31eSJames Morse } 554ad6eb31eSJames Morse 555f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 556f92b5462SJames Morse static void _local_event_register(void *data) 557f92b5462SJames Morse { 558f92b5462SJames Morse int err; 559f92b5462SJames Morse struct sdei_registered_event *reg; 560f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 561f92b5462SJames Morse 562f92b5462SJames Morse reg = per_cpu_ptr(arg->event->private_registered, smp_processor_id()); 563f92b5462SJames Morse err = sdei_api_event_register(arg->event->event_num, sdei_entry_point, 564f92b5462SJames Morse reg, 0, 0); 565f92b5462SJames Morse 566f92b5462SJames Morse sdei_cross_call_return(arg, err); 567f92b5462SJames Morse } 568f92b5462SJames Morse 569ad6eb31eSJames Morse int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) 570ad6eb31eSJames Morse { 571ad6eb31eSJames Morse int err; 572ad6eb31eSJames Morse struct sdei_event *event; 573ad6eb31eSJames Morse 574ad6eb31eSJames Morse WARN_ON(in_nmi()); 575ad6eb31eSJames Morse 576ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 577ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 578ad6eb31eSJames Morse pr_warn("Event %u already registered\n", event_num); 579ad6eb31eSJames Morse err = -EBUSY; 5801bbc7551SGavin Shan goto unlock; 581ad6eb31eSJames Morse } 582ad6eb31eSJames Morse 583ad6eb31eSJames Morse event = sdei_event_create(event_num, cb, arg); 584ad6eb31eSJames Morse if (IS_ERR(event)) { 585ad6eb31eSJames Morse err = PTR_ERR(event); 5861bbc7551SGavin Shan pr_warn("Failed to create event %u: %d\n", event_num, err); 5871bbc7551SGavin Shan goto unlock; 588ad6eb31eSJames Morse } 589ad6eb31eSJames Morse 59054f529a6SJames Morse cpus_read_lock(); 591d2fc580dSGavin Shan if (event->type == SDEI_EVENT_TYPE_SHARED) { 592d2fc580dSGavin Shan err = sdei_api_event_register(event->event_num, 593d2fc580dSGavin Shan sdei_entry_point, 594d2fc580dSGavin Shan event->registered, 595d2fc580dSGavin Shan SDEI_EVENT_REGISTER_RM_ANY, 0); 596d2fc580dSGavin Shan } else { 597d2fc580dSGavin Shan err = sdei_do_cross_call(_local_event_register, event); 598d2fc580dSGavin Shan if (err) 599d2fc580dSGavin Shan sdei_do_cross_call(_local_event_unregister, event); 600d2fc580dSGavin Shan } 601d2fc580dSGavin Shan 602ad6eb31eSJames Morse if (err) { 603ad6eb31eSJames Morse sdei_event_destroy(event); 6041bbc7551SGavin Shan pr_warn("Failed to register event %u: %d\n", event_num, err); 6051bbc7551SGavin Shan goto cpu_unlock; 6061bbc7551SGavin Shan } 6071bbc7551SGavin Shan 60854f529a6SJames Morse spin_lock(&sdei_list_lock); 60954f529a6SJames Morse event->reregister = true; 61054f529a6SJames Morse spin_unlock(&sdei_list_lock); 6111bbc7551SGavin Shan cpu_unlock: 61254f529a6SJames Morse cpus_read_unlock(); 6131bbc7551SGavin Shan unlock: 614ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 615ad6eb31eSJames Morse return err; 616ad6eb31eSJames Morse } 617ad6eb31eSJames Morse 618da351827SJames Morse static int sdei_reregister_shared(void) 619da351827SJames Morse { 620da351827SJames Morse int err = 0; 621da351827SJames Morse struct sdei_event *event; 622da351827SJames Morse 623da351827SJames Morse mutex_lock(&sdei_events_lock); 624da351827SJames Morse spin_lock(&sdei_list_lock); 625da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 626da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 627da351827SJames Morse continue; 628da351827SJames Morse 629da351827SJames Morse if (event->reregister) { 630d2fc580dSGavin Shan err = sdei_api_event_register(event->event_num, 631d2fc580dSGavin Shan sdei_entry_point, event->registered, 632d2fc580dSGavin Shan SDEI_EVENT_REGISTER_RM_ANY, 0); 633d2fc580dSGavin Shan if (err) { 634d2fc580dSGavin Shan pr_err("Failed to re-register event %u\n", 635d2fc580dSGavin Shan event->event_num); 636d2fc580dSGavin Shan sdei_event_destroy_llocked(event); 637da351827SJames Morse break; 638da351827SJames Morse } 639da351827SJames Morse } 640d2fc580dSGavin Shan 641d2fc580dSGavin Shan if (event->reenable) { 642d2fc580dSGavin Shan err = sdei_api_event_enable(event->event_num); 643d2fc580dSGavin Shan if (err) { 644d2fc580dSGavin Shan pr_err("Failed to re-enable event %u\n", 645d2fc580dSGavin Shan event->event_num); 646d2fc580dSGavin Shan break; 647d2fc580dSGavin Shan } 648d2fc580dSGavin Shan } 649d2fc580dSGavin Shan } 650da351827SJames Morse spin_unlock(&sdei_list_lock); 651da351827SJames Morse mutex_unlock(&sdei_events_lock); 652da351827SJames Morse 653da351827SJames Morse return err; 654da351827SJames Morse } 655da351827SJames Morse 656f92b5462SJames Morse static int sdei_cpuhp_down(unsigned int cpu) 657f92b5462SJames Morse { 658f92b5462SJames Morse struct sdei_event *event; 659f4673625SGavin Shan int err; 660f92b5462SJames Morse 661f92b5462SJames Morse /* un-register private events */ 662f92b5462SJames Morse spin_lock(&sdei_list_lock); 663f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 664f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 665f92b5462SJames Morse continue; 666f92b5462SJames Morse 667f4673625SGavin Shan err = sdei_do_local_call(_local_event_unregister, event); 668f4673625SGavin Shan if (err) { 669f92b5462SJames Morse pr_err("Failed to unregister event %u: %d\n", 670f4673625SGavin Shan event->event_num, err); 671f4673625SGavin Shan } 672f92b5462SJames Morse } 673f92b5462SJames Morse spin_unlock(&sdei_list_lock); 674f92b5462SJames Morse 675f92b5462SJames Morse return sdei_mask_local_cpu(); 676f92b5462SJames Morse } 677f92b5462SJames Morse 678f92b5462SJames Morse static int sdei_cpuhp_up(unsigned int cpu) 679f92b5462SJames Morse { 680f92b5462SJames Morse struct sdei_event *event; 681f4673625SGavin Shan int err; 682f92b5462SJames Morse 683f92b5462SJames Morse /* re-register/enable private events */ 684f92b5462SJames Morse spin_lock(&sdei_list_lock); 685f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 686f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 687f92b5462SJames Morse continue; 688f92b5462SJames Morse 689f92b5462SJames Morse if (event->reregister) { 690f4673625SGavin Shan err = sdei_do_local_call(_local_event_register, event); 691f4673625SGavin Shan if (err) { 692f92b5462SJames Morse pr_err("Failed to re-register event %u: %d\n", 693f4673625SGavin Shan event->event_num, err); 694f4673625SGavin Shan } 695f92b5462SJames Morse } 696f92b5462SJames Morse 697f92b5462SJames Morse if (event->reenable) { 698f4673625SGavin Shan err = sdei_do_local_call(_local_event_enable, event); 699f4673625SGavin Shan if (err) { 700f92b5462SJames Morse pr_err("Failed to re-enable event %u: %d\n", 701f4673625SGavin Shan event->event_num, err); 702f4673625SGavin Shan } 703f92b5462SJames Morse } 704f92b5462SJames Morse } 705f92b5462SJames Morse spin_unlock(&sdei_list_lock); 706f92b5462SJames Morse 707f92b5462SJames Morse return sdei_unmask_local_cpu(); 708f92b5462SJames Morse } 709f92b5462SJames Morse 710da351827SJames Morse /* When entering idle, mask/unmask events for this cpu */ 711da351827SJames Morse static int sdei_pm_notifier(struct notifier_block *nb, unsigned long action, 712da351827SJames Morse void *data) 713da351827SJames Morse { 714da351827SJames Morse int rv; 715da351827SJames Morse 716d2c48b23SPierre Gondois WARN_ON_ONCE(preemptible()); 717d2c48b23SPierre Gondois 718da351827SJames Morse switch (action) { 719da351827SJames Morse case CPU_PM_ENTER: 720da351827SJames Morse rv = sdei_mask_local_cpu(); 721da351827SJames Morse break; 722da351827SJames Morse case CPU_PM_EXIT: 723da351827SJames Morse case CPU_PM_ENTER_FAILED: 724da351827SJames Morse rv = sdei_unmask_local_cpu(); 725da351827SJames Morse break; 726da351827SJames Morse default: 727da351827SJames Morse return NOTIFY_DONE; 728da351827SJames Morse } 729da351827SJames Morse 730da351827SJames Morse if (rv) 731da351827SJames Morse return notifier_from_errno(rv); 732da351827SJames Morse 733da351827SJames Morse return NOTIFY_OK; 734da351827SJames Morse } 735da351827SJames Morse 736da351827SJames Morse static struct notifier_block sdei_pm_nb = { 737da351827SJames Morse .notifier_call = sdei_pm_notifier, 738da351827SJames Morse }; 739da351827SJames Morse 740da351827SJames Morse static int sdei_device_suspend(struct device *dev) 741da351827SJames Morse { 742da351827SJames Morse on_each_cpu(_ipi_mask_cpu, NULL, true); 743da351827SJames Morse 744da351827SJames Morse return 0; 745da351827SJames Morse } 746da351827SJames Morse 747da351827SJames Morse static int sdei_device_resume(struct device *dev) 748da351827SJames Morse { 749da351827SJames Morse on_each_cpu(_ipi_unmask_cpu, NULL, true); 750da351827SJames Morse 751da351827SJames Morse return 0; 752da351827SJames Morse } 753da351827SJames Morse 754da351827SJames Morse /* 755da351827SJames Morse * We need all events to be reregistered when we resume from hibernate. 756da351827SJames Morse * 757da351827SJames Morse * The sequence is freeze->thaw. Reboot. freeze->restore. We unregister 758da351827SJames Morse * events during freeze, then re-register and re-enable them during thaw 759da351827SJames Morse * and restore. 760da351827SJames Morse */ 761da351827SJames Morse static int sdei_device_freeze(struct device *dev) 762da351827SJames Morse { 763da351827SJames Morse int err; 764da351827SJames Morse 765f92b5462SJames Morse /* unregister private events */ 766*a6fd7862SXiongfeng Wang cpuhp_remove_state(sdei_hp_state); 767da351827SJames Morse 768da351827SJames Morse err = sdei_unregister_shared(); 769da351827SJames Morse if (err) 770da351827SJames Morse return err; 771da351827SJames Morse 772da351827SJames Morse return 0; 773da351827SJames Morse } 774da351827SJames Morse 775da351827SJames Morse static int sdei_device_thaw(struct device *dev) 776da351827SJames Morse { 777da351827SJames Morse int err; 778da351827SJames Morse 779da351827SJames Morse /* re-register shared events */ 780da351827SJames Morse err = sdei_reregister_shared(); 781da351827SJames Morse if (err) { 782da351827SJames Morse pr_warn("Failed to re-register shared events...\n"); 783da351827SJames Morse sdei_mark_interface_broken(); 784da351827SJames Morse return err; 785da351827SJames Morse } 786da351827SJames Morse 787d2c48b23SPierre Gondois err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "SDEI", 788da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 789d2c48b23SPierre Gondois if (err < 0) { 790da351827SJames Morse pr_warn("Failed to re-register CPU hotplug notifier...\n"); 791da351827SJames Morse return err; 792da351827SJames Morse } 793da351827SJames Morse 794d2c48b23SPierre Gondois sdei_hp_state = err; 795d2c48b23SPierre Gondois return 0; 796d2c48b23SPierre Gondois } 797d2c48b23SPierre Gondois 798da351827SJames Morse static int sdei_device_restore(struct device *dev) 799da351827SJames Morse { 800da351827SJames Morse int err; 801da351827SJames Morse 802da351827SJames Morse err = sdei_platform_reset(); 803da351827SJames Morse if (err) 804da351827SJames Morse return err; 805da351827SJames Morse 806da351827SJames Morse return sdei_device_thaw(dev); 807da351827SJames Morse } 808da351827SJames Morse 809da351827SJames Morse static const struct dev_pm_ops sdei_pm_ops = { 810da351827SJames Morse .suspend = sdei_device_suspend, 811da351827SJames Morse .resume = sdei_device_resume, 812da351827SJames Morse .freeze = sdei_device_freeze, 813da351827SJames Morse .thaw = sdei_device_thaw, 814da351827SJames Morse .restore = sdei_device_restore, 815da351827SJames Morse }; 816da351827SJames Morse 817da351827SJames Morse /* 818da351827SJames Morse * Mask all CPUs and unregister all events on panic, reboot or kexec. 819da351827SJames Morse */ 820da351827SJames Morse static int sdei_reboot_notifier(struct notifier_block *nb, unsigned long action, 821da351827SJames Morse void *data) 822da351827SJames Morse { 823da351827SJames Morse /* 824da351827SJames Morse * We are going to reset the interface, after this there is no point 825da351827SJames Morse * doing work when we take CPUs offline. 826da351827SJames Morse */ 827d2c48b23SPierre Gondois cpuhp_remove_state(sdei_hp_state); 828da351827SJames Morse 829da351827SJames Morse sdei_platform_reset(); 830da351827SJames Morse 831da351827SJames Morse return NOTIFY_OK; 832da351827SJames Morse } 833da351827SJames Morse 834da351827SJames Morse static struct notifier_block sdei_reboot_nb = { 835da351827SJames Morse .notifier_call = sdei_reboot_notifier, 836da351827SJames Morse }; 837da351827SJames Morse 838ad6eb31eSJames Morse static void sdei_smccc_smc(unsigned long function_id, 839ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 840ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 841ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 842ad6eb31eSJames Morse { 843ad6eb31eSJames Morse arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 844ad6eb31eSJames Morse } 8452f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_smc); 846ad6eb31eSJames Morse 847ad6eb31eSJames Morse static void sdei_smccc_hvc(unsigned long function_id, 848ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 849ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 850ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 851ad6eb31eSJames Morse { 852ad6eb31eSJames Morse arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 853ad6eb31eSJames Morse } 8542f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_hvc); 855ad6eb31eSJames Morse 856f96935d3SJames Morse int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, 857f96935d3SJames Morse sdei_event_callback *critical_cb) 858f96935d3SJames Morse { 859f96935d3SJames Morse int err; 860f96935d3SJames Morse u64 result; 861f96935d3SJames Morse u32 event_num; 862f96935d3SJames Morse sdei_event_callback *cb; 863f96935d3SJames Morse 864f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 865f96935d3SJames Morse return -EOPNOTSUPP; 866f96935d3SJames Morse 867f96935d3SJames Morse event_num = ghes->generic->notify.vector; 868f96935d3SJames Morse if (event_num == 0) { 869f96935d3SJames Morse /* 870f96935d3SJames Morse * Event 0 is reserved by the specification for 871f96935d3SJames Morse * SDEI_EVENT_SIGNAL. 872f96935d3SJames Morse */ 873f96935d3SJames Morse return -EINVAL; 874f96935d3SJames Morse } 875f96935d3SJames Morse 876f96935d3SJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 877f96935d3SJames Morse &result); 878f96935d3SJames Morse if (err) 879f96935d3SJames Morse return err; 880f96935d3SJames Morse 881f96935d3SJames Morse if (result == SDEI_EVENT_PRIORITY_CRITICAL) 882f96935d3SJames Morse cb = critical_cb; 883f96935d3SJames Morse else 884f96935d3SJames Morse cb = normal_cb; 885f96935d3SJames Morse 886f96935d3SJames Morse err = sdei_event_register(event_num, cb, ghes); 887f96935d3SJames Morse if (!err) 888f96935d3SJames Morse err = sdei_event_enable(event_num); 889f96935d3SJames Morse 890f96935d3SJames Morse return err; 891f96935d3SJames Morse } 892f96935d3SJames Morse 893f96935d3SJames Morse int sdei_unregister_ghes(struct ghes *ghes) 894f96935d3SJames Morse { 895f96935d3SJames Morse int i; 896f96935d3SJames Morse int err; 897f96935d3SJames Morse u32 event_num = ghes->generic->notify.vector; 898f96935d3SJames Morse 899f96935d3SJames Morse might_sleep(); 900f96935d3SJames Morse 901f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 902f96935d3SJames Morse return -EOPNOTSUPP; 903f96935d3SJames Morse 904f96935d3SJames Morse /* 905f96935d3SJames Morse * The event may be running on another CPU. Disable it 906f96935d3SJames Morse * to stop new events, then try to unregister a few times. 907f96935d3SJames Morse */ 908f96935d3SJames Morse err = sdei_event_disable(event_num); 909f96935d3SJames Morse if (err) 910f96935d3SJames Morse return err; 911f96935d3SJames Morse 912f96935d3SJames Morse for (i = 0; i < 3; i++) { 913f96935d3SJames Morse err = sdei_event_unregister(event_num); 914f96935d3SJames Morse if (err != -EINPROGRESS) 915f96935d3SJames Morse break; 916f96935d3SJames Morse 917f96935d3SJames Morse schedule(); 918f96935d3SJames Morse } 919f96935d3SJames Morse 920f96935d3SJames Morse return err; 921f96935d3SJames Morse } 922f96935d3SJames Morse 923ad6eb31eSJames Morse static int sdei_get_conduit(struct platform_device *pdev) 924ad6eb31eSJames Morse { 925ad6eb31eSJames Morse const char *method; 926ad6eb31eSJames Morse struct device_node *np = pdev->dev.of_node; 927ad6eb31eSJames Morse 928ad6eb31eSJames Morse sdei_firmware_call = NULL; 929ad6eb31eSJames Morse if (np) { 930ad6eb31eSJames Morse if (of_property_read_string(np, "method", &method)) { 931ad6eb31eSJames Morse pr_warn("missing \"method\" property\n"); 932e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 933ad6eb31eSJames Morse } 934ad6eb31eSJames Morse 935ad6eb31eSJames Morse if (!strcmp("hvc", method)) { 936ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 937e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 938ad6eb31eSJames Morse } else if (!strcmp("smc", method)) { 939ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_smc; 940e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 941ad6eb31eSJames Morse } 942ad6eb31eSJames Morse 943ad6eb31eSJames Morse pr_warn("invalid \"method\" property: %s\n", method); 944bc110fd3SGavin Shan } else if (!acpi_disabled) { 945677a60bdSJames Morse if (acpi_psci_use_hvc()) { 946677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 947e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 948677a60bdSJames Morse } else { 949677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_smc; 950e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 951677a60bdSJames Morse } 952ad6eb31eSJames Morse } 953ad6eb31eSJames Morse 954e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 955ad6eb31eSJames Morse } 956ad6eb31eSJames Morse 957ad6eb31eSJames Morse static int sdei_probe(struct platform_device *pdev) 958ad6eb31eSJames Morse { 959ad6eb31eSJames Morse int err; 960ad6eb31eSJames Morse u64 ver = 0; 961ad6eb31eSJames Morse int conduit; 962ad6eb31eSJames Morse 963ad6eb31eSJames Morse conduit = sdei_get_conduit(pdev); 964ad6eb31eSJames Morse if (!sdei_firmware_call) 965ad6eb31eSJames Morse return 0; 966ad6eb31eSJames Morse 967ad6eb31eSJames Morse err = sdei_api_get_version(&ver); 968ad6eb31eSJames Morse if (err) { 969ad6eb31eSJames Morse pr_err("Failed to get SDEI version: %d\n", err); 970ad6eb31eSJames Morse sdei_mark_interface_broken(); 971ad6eb31eSJames Morse return err; 972ad6eb31eSJames Morse } 973ad6eb31eSJames Morse 974ad6eb31eSJames Morse pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n", 975ad6eb31eSJames Morse (int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver), 976ad6eb31eSJames Morse (int)SDEI_VERSION_VENDOR(ver)); 977ad6eb31eSJames Morse 978ad6eb31eSJames Morse if (SDEI_VERSION_MAJOR(ver) != 1) { 979ad6eb31eSJames Morse pr_warn("Conflicting SDEI version detected.\n"); 980ad6eb31eSJames Morse sdei_mark_interface_broken(); 981ad6eb31eSJames Morse return -EINVAL; 982ad6eb31eSJames Morse } 983ad6eb31eSJames Morse 984ad6eb31eSJames Morse err = sdei_platform_reset(); 985ad6eb31eSJames Morse if (err) 986ad6eb31eSJames Morse return err; 987ad6eb31eSJames Morse 988ad6eb31eSJames Morse sdei_entry_point = sdei_arch_get_entry_point(conduit); 989ad6eb31eSJames Morse if (!sdei_entry_point) { 990ad6eb31eSJames Morse /* Not supported due to hardware or boot configuration */ 991ad6eb31eSJames Morse sdei_mark_interface_broken(); 992ad6eb31eSJames Morse return 0; 993ad6eb31eSJames Morse } 994ad6eb31eSJames Morse 995da351827SJames Morse err = cpu_pm_register_notifier(&sdei_pm_nb); 996da351827SJames Morse if (err) { 997da351827SJames Morse pr_warn("Failed to register CPU PM notifier...\n"); 998da351827SJames Morse goto error; 999da351827SJames Morse } 1000da351827SJames Morse 1001da351827SJames Morse err = register_reboot_notifier(&sdei_reboot_nb); 1002da351827SJames Morse if (err) { 1003da351827SJames Morse pr_warn("Failed to register reboot notifier...\n"); 1004da351827SJames Morse goto remove_cpupm; 1005da351827SJames Morse } 1006da351827SJames Morse 1007d2c48b23SPierre Gondois err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "SDEI", 1008da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 1009d2c48b23SPierre Gondois if (err < 0) { 1010da351827SJames Morse pr_warn("Failed to register CPU hotplug notifier...\n"); 1011da351827SJames Morse goto remove_reboot; 1012da351827SJames Morse } 1013ad6eb31eSJames Morse 1014d2c48b23SPierre Gondois sdei_hp_state = err; 1015d2c48b23SPierre Gondois 1016ad6eb31eSJames Morse return 0; 1017da351827SJames Morse 1018da351827SJames Morse remove_reboot: 1019da351827SJames Morse unregister_reboot_notifier(&sdei_reboot_nb); 1020da351827SJames Morse 1021da351827SJames Morse remove_cpupm: 1022da351827SJames Morse cpu_pm_unregister_notifier(&sdei_pm_nb); 1023da351827SJames Morse 1024da351827SJames Morse error: 1025da351827SJames Morse sdei_mark_interface_broken(); 1026da351827SJames Morse return err; 1027ad6eb31eSJames Morse } 1028ad6eb31eSJames Morse 1029ad6eb31eSJames Morse static const struct of_device_id sdei_of_match[] = { 1030ad6eb31eSJames Morse { .compatible = "arm,sdei-1.0" }, 1031ad6eb31eSJames Morse {} 1032ad6eb31eSJames Morse }; 1033ad6eb31eSJames Morse 1034ad6eb31eSJames Morse static struct platform_driver sdei_driver = { 1035ad6eb31eSJames Morse .driver = { 1036ad6eb31eSJames Morse .name = "sdei", 1037da351827SJames Morse .pm = &sdei_pm_ops, 1038ad6eb31eSJames Morse .of_match_table = sdei_of_match, 1039ad6eb31eSJames Morse }, 1040ad6eb31eSJames Morse .probe = sdei_probe, 1041ad6eb31eSJames Morse }; 1042ad6eb31eSJames Morse 1043677a60bdSJames Morse static bool __init sdei_present_acpi(void) 1044677a60bdSJames Morse { 1045677a60bdSJames Morse acpi_status status; 1046677a60bdSJames Morse struct acpi_table_header *sdei_table_header; 1047677a60bdSJames Morse 1048677a60bdSJames Morse if (acpi_disabled) 1049677a60bdSJames Morse return false; 1050677a60bdSJames Morse 1051677a60bdSJames Morse status = acpi_get_table(ACPI_SIG_SDEI, 0, &sdei_table_header); 1052677a60bdSJames Morse if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { 1053677a60bdSJames Morse const char *msg = acpi_format_exception(status); 1054677a60bdSJames Morse 1055677a60bdSJames Morse pr_info("Failed to get ACPI:SDEI table, %s\n", msg); 1056677a60bdSJames Morse } 1057677a60bdSJames Morse if (ACPI_FAILURE(status)) 1058677a60bdSJames Morse return false; 1059677a60bdSJames Morse 106070e6352aSHanjun Guo acpi_put_table(sdei_table_header); 106170e6352aSHanjun Guo 1062677a60bdSJames Morse return true; 1063677a60bdSJames Morse } 1064677a60bdSJames Morse 1065dc4e8c07SShuai Xue void __init sdei_init(void) 1066ad6eb31eSJames Morse { 1067caf2cd61SSudeep Holla struct platform_device *pdev; 106810fd7c42SGavin Shan int ret; 106910fd7c42SGavin Shan 107010fd7c42SGavin Shan ret = platform_driver_register(&sdei_driver); 107110fd7c42SGavin Shan if (ret || !sdei_present_acpi()) 1072dc4e8c07SShuai Xue return; 1073caf2cd61SSudeep Holla 1074caf2cd61SSudeep Holla pdev = platform_device_register_simple(sdei_driver.driver.name, 1075caf2cd61SSudeep Holla 0, NULL, 0); 107663627caeSGavin Shan if (IS_ERR(pdev)) { 107763627caeSGavin Shan ret = PTR_ERR(pdev); 107863627caeSGavin Shan platform_driver_unregister(&sdei_driver); 107963627caeSGavin Shan pr_info("Failed to register ACPI:SDEI platform device %d\n", 108063627caeSGavin Shan ret); 108163627caeSGavin Shan } 1082ad6eb31eSJames Morse } 1083ad6eb31eSJames Morse 1084ad6eb31eSJames Morse int sdei_event_handler(struct pt_regs *regs, 1085ad6eb31eSJames Morse struct sdei_registered_event *arg) 1086ad6eb31eSJames Morse { 1087ad6eb31eSJames Morse int err; 1088ad6eb31eSJames Morse u32 event_num = arg->event_num; 1089ad6eb31eSJames Morse 1090ad6eb31eSJames Morse err = arg->callback(event_num, regs, arg->callback_arg); 1091ad6eb31eSJames Morse if (err) 1092ad6eb31eSJames Morse pr_err_ratelimited("event %u on CPU %u failed with error: %d\n", 1093ad6eb31eSJames Morse event_num, smp_processor_id(), err); 1094ad6eb31eSJames Morse 1095ad6eb31eSJames Morse return err; 1096ad6eb31eSJames Morse } 1097ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_event_handler); 10985cd474e5SD Scott Phillips 10995cd474e5SD Scott Phillips void sdei_handler_abort(void) 11005cd474e5SD Scott Phillips { 11015cd474e5SD Scott Phillips /* 11025cd474e5SD Scott Phillips * If the crash happened in an SDEI event handler then we need to 11035cd474e5SD Scott Phillips * finish the handler with the firmware so that we can have working 11045cd474e5SD Scott Phillips * interrupts in the crash kernel. 11055cd474e5SD Scott Phillips */ 11065cd474e5SD Scott Phillips if (__this_cpu_read(sdei_active_critical_event)) { 11075cd474e5SD Scott Phillips pr_warn("still in SDEI critical event context, attempting to finish handler.\n"); 11085cd474e5SD Scott Phillips __sdei_handler_abort(); 11095cd474e5SD Scott Phillips __this_cpu_write(sdei_active_critical_event, NULL); 11105cd474e5SD Scott Phillips } 11115cd474e5SD Scott Phillips if (__this_cpu_read(sdei_active_normal_event)) { 11125cd474e5SD Scott Phillips pr_warn("still in SDEI normal event context, attempting to finish handler.\n"); 11135cd474e5SD Scott Phillips __sdei_handler_abort(); 11145cd474e5SD Scott Phillips __this_cpu_write(sdei_active_normal_event, NULL); 11155cd474e5SD Scott Phillips } 11165cd474e5SD Scott Phillips } 1117