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 #include <linux/uaccess.h> 35ad6eb31eSJames Morse 36ad6eb31eSJames Morse /* 37ad6eb31eSJames Morse * The call to use to reach the firmware. 38ad6eb31eSJames Morse */ 39ad6eb31eSJames Morse static asmlinkage void (*sdei_firmware_call)(unsigned long function_id, 40ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 41ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 42ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res); 43ad6eb31eSJames Morse 44ad6eb31eSJames Morse /* entry point from firmware to arch asm code */ 45ad6eb31eSJames Morse static unsigned long sdei_entry_point; 46ad6eb31eSJames Morse 47ad6eb31eSJames Morse struct sdei_event { 48da351827SJames Morse /* These three are protected by the sdei_list_lock */ 49ad6eb31eSJames Morse struct list_head list; 50da351827SJames Morse bool reregister; 51da351827SJames Morse bool reenable; 52da351827SJames Morse 53ad6eb31eSJames Morse u32 event_num; 54ad6eb31eSJames Morse u8 type; 55ad6eb31eSJames Morse u8 priority; 56ad6eb31eSJames Morse 57ad6eb31eSJames Morse /* This pointer is handed to firmware as the event argument. */ 58f92b5462SJames Morse union { 59f92b5462SJames Morse /* Shared events */ 60ad6eb31eSJames Morse struct sdei_registered_event *registered; 61f92b5462SJames Morse 62f92b5462SJames Morse /* CPU private events */ 63f92b5462SJames Morse struct sdei_registered_event __percpu *private_registered; 64f92b5462SJames Morse }; 65ad6eb31eSJames Morse }; 66ad6eb31eSJames Morse 67ad6eb31eSJames Morse /* Take the mutex for any API call or modification. Take the mutex first. */ 68ad6eb31eSJames Morse static DEFINE_MUTEX(sdei_events_lock); 69ad6eb31eSJames Morse 70ad6eb31eSJames Morse /* and then hold this when modifying the list */ 71ad6eb31eSJames Morse static DEFINE_SPINLOCK(sdei_list_lock); 72ad6eb31eSJames Morse static LIST_HEAD(sdei_list); 73ad6eb31eSJames Morse 74f92b5462SJames Morse /* Private events are registered/enabled via IPI passing one of these */ 75f92b5462SJames Morse struct sdei_crosscall_args { 76f92b5462SJames Morse struct sdei_event *event; 77f92b5462SJames Morse atomic_t errors; 78f92b5462SJames Morse int first_error; 79f92b5462SJames Morse }; 80f92b5462SJames Morse 81*a27c04e1SGavin Shan #define CROSSCALL_INIT(arg, event) \ 82*a27c04e1SGavin Shan do { \ 83*a27c04e1SGavin Shan arg.event = event; \ 84*a27c04e1SGavin Shan arg.first_error = 0; \ 85*a27c04e1SGavin Shan atomic_set(&arg.errors, 0); \ 86*a27c04e1SGavin Shan } while (0) 87f92b5462SJames Morse 88*a27c04e1SGavin Shan static inline int sdei_do_cross_call(smp_call_func_t fn, 89*a27c04e1SGavin Shan struct sdei_event *event) 90f92b5462SJames Morse { 91f92b5462SJames Morse struct sdei_crosscall_args arg; 92f92b5462SJames Morse 93f92b5462SJames Morse CROSSCALL_INIT(arg, event); 94f92b5462SJames Morse on_each_cpu(fn, &arg, true); 95f92b5462SJames Morse 96f92b5462SJames Morse return arg.first_error; 97f92b5462SJames Morse } 98f92b5462SJames Morse 99f92b5462SJames Morse static inline void 100f92b5462SJames Morse sdei_cross_call_return(struct sdei_crosscall_args *arg, int err) 101f92b5462SJames Morse { 102f92b5462SJames Morse if (err && (atomic_inc_return(&arg->errors) == 1)) 103f92b5462SJames Morse arg->first_error = err; 104f92b5462SJames Morse } 105f92b5462SJames Morse 106ad6eb31eSJames Morse static int sdei_to_linux_errno(unsigned long sdei_err) 107ad6eb31eSJames Morse { 108ad6eb31eSJames Morse switch (sdei_err) { 109ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 110ad6eb31eSJames Morse return -EOPNOTSUPP; 111ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 112ad6eb31eSJames Morse return -EINVAL; 113ad6eb31eSJames Morse case SDEI_DENIED: 114ad6eb31eSJames Morse return -EPERM; 115ad6eb31eSJames Morse case SDEI_PENDING: 116ad6eb31eSJames Morse return -EINPROGRESS; 117ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 118ad6eb31eSJames Morse return -ENOMEM; 119ad6eb31eSJames Morse } 120ad6eb31eSJames Morse 1215735f515SGavin Shan return 0; 122ad6eb31eSJames Morse } 123ad6eb31eSJames Morse 124ad6eb31eSJames Morse static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0, 125ad6eb31eSJames Morse unsigned long arg1, unsigned long arg2, 126ad6eb31eSJames Morse unsigned long arg3, unsigned long arg4, 127ad6eb31eSJames Morse u64 *result) 128ad6eb31eSJames Morse { 1295735f515SGavin Shan int err; 130ad6eb31eSJames Morse struct arm_smccc_res res; 131ad6eb31eSJames Morse 132ad6eb31eSJames Morse if (sdei_firmware_call) { 133ad6eb31eSJames Morse sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4, 134ad6eb31eSJames Morse &res); 135ad6eb31eSJames Morse err = sdei_to_linux_errno(res.a0); 136ad6eb31eSJames Morse } else { 137ad6eb31eSJames Morse /* 138ad6eb31eSJames Morse * !sdei_firmware_call means we failed to probe or called 139ad6eb31eSJames Morse * sdei_mark_interface_broken(). -EIO is not an error returned 140ad6eb31eSJames Morse * by sdei_to_linux_errno() and is used to suppress messages 141ad6eb31eSJames Morse * from this driver. 142ad6eb31eSJames Morse */ 143ad6eb31eSJames Morse err = -EIO; 144ad6eb31eSJames Morse res.a0 = SDEI_NOT_SUPPORTED; 145ad6eb31eSJames Morse } 146ad6eb31eSJames Morse 147ad6eb31eSJames Morse if (result) 148ad6eb31eSJames Morse *result = res.a0; 149ad6eb31eSJames Morse 150ad6eb31eSJames Morse return err; 151ad6eb31eSJames Morse } 1522f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(invoke_sdei_fn); 153ad6eb31eSJames Morse 154ad6eb31eSJames Morse static struct sdei_event *sdei_event_find(u32 event_num) 155ad6eb31eSJames Morse { 156ad6eb31eSJames Morse struct sdei_event *e, *found = NULL; 157ad6eb31eSJames Morse 158ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 159ad6eb31eSJames Morse 160ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 161ad6eb31eSJames Morse list_for_each_entry(e, &sdei_list, list) { 162ad6eb31eSJames Morse if (e->event_num == event_num) { 163ad6eb31eSJames Morse found = e; 164ad6eb31eSJames Morse break; 165ad6eb31eSJames Morse } 166ad6eb31eSJames Morse } 167ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 168ad6eb31eSJames Morse 169ad6eb31eSJames Morse return found; 170ad6eb31eSJames Morse } 171ad6eb31eSJames Morse 172ad6eb31eSJames Morse int sdei_api_event_context(u32 query, u64 *result) 173ad6eb31eSJames Morse { 174ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0, 175ad6eb31eSJames Morse result); 176ad6eb31eSJames Morse } 177ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_api_event_context); 178ad6eb31eSJames Morse 179ad6eb31eSJames Morse static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) 180ad6eb31eSJames Morse { 181ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, 182ad6eb31eSJames Morse 0, 0, result); 183ad6eb31eSJames Morse } 184ad6eb31eSJames Morse 185ad6eb31eSJames Morse static struct sdei_event *sdei_event_create(u32 event_num, 186ad6eb31eSJames Morse sdei_event_callback *cb, 187ad6eb31eSJames Morse void *cb_arg) 188ad6eb31eSJames Morse { 189ad6eb31eSJames Morse int err; 190ad6eb31eSJames Morse u64 result; 191ad6eb31eSJames Morse struct sdei_event *event; 192ad6eb31eSJames Morse struct sdei_registered_event *reg; 193ad6eb31eSJames Morse 194ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 195ad6eb31eSJames Morse 196ad6eb31eSJames Morse event = kzalloc(sizeof(*event), GFP_KERNEL); 19711988424SGavin Shan if (!event) { 19811988424SGavin Shan err = -ENOMEM; 19911988424SGavin Shan goto fail; 20011988424SGavin Shan } 201ad6eb31eSJames Morse 202ad6eb31eSJames Morse INIT_LIST_HEAD(&event->list); 203ad6eb31eSJames Morse event->event_num = event_num; 204ad6eb31eSJames Morse 205ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 206ad6eb31eSJames Morse &result); 20711988424SGavin Shan if (err) 20811988424SGavin Shan goto fail; 209ad6eb31eSJames Morse event->priority = result; 210ad6eb31eSJames Morse 211ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE, 212ad6eb31eSJames Morse &result); 21311988424SGavin Shan if (err) 21411988424SGavin Shan goto fail; 215ad6eb31eSJames Morse event->type = result; 216ad6eb31eSJames Morse 217ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) { 218ad6eb31eSJames Morse reg = kzalloc(sizeof(*reg), GFP_KERNEL); 219ad6eb31eSJames Morse if (!reg) { 22011988424SGavin Shan err = -ENOMEM; 22111988424SGavin Shan goto fail; 222ad6eb31eSJames Morse } 223ad6eb31eSJames Morse 224663c0e89SGavin Shan reg->event_num = event->event_num; 225ad6eb31eSJames Morse reg->priority = event->priority; 226ad6eb31eSJames Morse 227ad6eb31eSJames Morse reg->callback = cb; 228ad6eb31eSJames Morse reg->callback_arg = cb_arg; 229ad6eb31eSJames Morse event->registered = reg; 230f92b5462SJames Morse } else { 231f92b5462SJames Morse int cpu; 232f92b5462SJames Morse struct sdei_registered_event __percpu *regs; 233f92b5462SJames Morse 234f92b5462SJames Morse regs = alloc_percpu(struct sdei_registered_event); 235f92b5462SJames Morse if (!regs) { 23611988424SGavin Shan err = -ENOMEM; 23711988424SGavin Shan goto fail; 238f92b5462SJames Morse } 239f92b5462SJames Morse 240f92b5462SJames Morse for_each_possible_cpu(cpu) { 241f92b5462SJames Morse reg = per_cpu_ptr(regs, cpu); 242f92b5462SJames Morse 243f92b5462SJames Morse reg->event_num = event->event_num; 244f92b5462SJames Morse reg->priority = event->priority; 245f92b5462SJames Morse reg->callback = cb; 246f92b5462SJames Morse reg->callback_arg = cb_arg; 247f92b5462SJames Morse } 248f92b5462SJames Morse 249f92b5462SJames Morse event->private_registered = regs; 250ad6eb31eSJames Morse } 251ad6eb31eSJames Morse 252ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 253ad6eb31eSJames Morse list_add(&event->list, &sdei_list); 254ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 255ad6eb31eSJames Morse 256ad6eb31eSJames Morse return event; 25711988424SGavin Shan 25811988424SGavin Shan fail: 25911988424SGavin Shan kfree(event); 26011988424SGavin Shan return ERR_PTR(err); 261ad6eb31eSJames Morse } 262ad6eb31eSJames Morse 263c66d52b1SLiguang Zhang static void sdei_event_destroy_llocked(struct sdei_event *event) 264ad6eb31eSJames Morse { 265ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 266c66d52b1SLiguang Zhang lockdep_assert_held(&sdei_list_lock); 267ad6eb31eSJames Morse 268ad6eb31eSJames Morse list_del(&event->list); 269ad6eb31eSJames Morse 270ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 271ad6eb31eSJames Morse kfree(event->registered); 272f92b5462SJames Morse else 273f92b5462SJames Morse free_percpu(event->private_registered); 274ad6eb31eSJames Morse 275ad6eb31eSJames Morse kfree(event); 276ad6eb31eSJames Morse } 277ad6eb31eSJames Morse 278c66d52b1SLiguang Zhang static void sdei_event_destroy(struct sdei_event *event) 279c66d52b1SLiguang Zhang { 280c66d52b1SLiguang Zhang spin_lock(&sdei_list_lock); 281c66d52b1SLiguang Zhang sdei_event_destroy_llocked(event); 282c66d52b1SLiguang Zhang spin_unlock(&sdei_list_lock); 283c66d52b1SLiguang Zhang } 284c66d52b1SLiguang Zhang 285ad6eb31eSJames Morse static int sdei_api_get_version(u64 *version) 286ad6eb31eSJames Morse { 287ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); 288ad6eb31eSJames Morse } 289ad6eb31eSJames Morse 290ad6eb31eSJames Morse int sdei_mask_local_cpu(void) 291ad6eb31eSJames Morse { 292ad6eb31eSJames Morse int err; 293ad6eb31eSJames Morse 294ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 295ad6eb31eSJames Morse 296ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL); 297ad6eb31eSJames Morse if (err && err != -EIO) { 298ad6eb31eSJames Morse pr_warn_once("failed to mask CPU[%u]: %d\n", 299ad6eb31eSJames Morse smp_processor_id(), err); 300ad6eb31eSJames Morse return err; 301ad6eb31eSJames Morse } 302ad6eb31eSJames Morse 303ad6eb31eSJames Morse return 0; 304ad6eb31eSJames Morse } 305ad6eb31eSJames Morse 306ad6eb31eSJames Morse static void _ipi_mask_cpu(void *ignored) 307ad6eb31eSJames Morse { 308ad6eb31eSJames Morse sdei_mask_local_cpu(); 309ad6eb31eSJames Morse } 310ad6eb31eSJames Morse 311ad6eb31eSJames Morse int sdei_unmask_local_cpu(void) 312ad6eb31eSJames Morse { 313ad6eb31eSJames Morse int err; 314ad6eb31eSJames Morse 315ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 316ad6eb31eSJames Morse 317ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL); 318ad6eb31eSJames Morse if (err && err != -EIO) { 319ad6eb31eSJames Morse pr_warn_once("failed to unmask CPU[%u]: %d\n", 320ad6eb31eSJames Morse smp_processor_id(), err); 321ad6eb31eSJames Morse return err; 322ad6eb31eSJames Morse } 323ad6eb31eSJames Morse 324ad6eb31eSJames Morse return 0; 325ad6eb31eSJames Morse } 326ad6eb31eSJames Morse 327ad6eb31eSJames Morse static void _ipi_unmask_cpu(void *ignored) 328ad6eb31eSJames Morse { 329ad6eb31eSJames Morse sdei_unmask_local_cpu(); 330ad6eb31eSJames Morse } 331ad6eb31eSJames Morse 332ad6eb31eSJames Morse static void _ipi_private_reset(void *ignored) 333ad6eb31eSJames Morse { 334ad6eb31eSJames Morse int err; 335ad6eb31eSJames Morse 336ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0, 337ad6eb31eSJames Morse NULL); 338ad6eb31eSJames Morse if (err && err != -EIO) 339ad6eb31eSJames Morse pr_warn_once("failed to reset CPU[%u]: %d\n", 340ad6eb31eSJames Morse smp_processor_id(), err); 341ad6eb31eSJames Morse } 342ad6eb31eSJames Morse 343ad6eb31eSJames Morse static int sdei_api_shared_reset(void) 344ad6eb31eSJames Morse { 345ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0, 346ad6eb31eSJames Morse NULL); 347ad6eb31eSJames Morse } 348ad6eb31eSJames Morse 349ad6eb31eSJames Morse static void sdei_mark_interface_broken(void) 350ad6eb31eSJames Morse { 351ad6eb31eSJames Morse pr_err("disabling SDEI firmware interface\n"); 352ad6eb31eSJames Morse on_each_cpu(&_ipi_mask_cpu, NULL, true); 353ad6eb31eSJames Morse sdei_firmware_call = NULL; 354ad6eb31eSJames Morse } 355ad6eb31eSJames Morse 356ad6eb31eSJames Morse static int sdei_platform_reset(void) 357ad6eb31eSJames Morse { 358ad6eb31eSJames Morse int err; 359ad6eb31eSJames Morse 360ad6eb31eSJames Morse on_each_cpu(&_ipi_private_reset, NULL, true); 361ad6eb31eSJames Morse err = sdei_api_shared_reset(); 362ad6eb31eSJames Morse if (err) { 363ad6eb31eSJames Morse pr_err("Failed to reset platform: %d\n", err); 364ad6eb31eSJames Morse sdei_mark_interface_broken(); 365ad6eb31eSJames Morse } 366ad6eb31eSJames Morse 367ad6eb31eSJames Morse return err; 368ad6eb31eSJames Morse } 369ad6eb31eSJames Morse 370ad6eb31eSJames Morse static int sdei_api_event_enable(u32 event_num) 371ad6eb31eSJames Morse { 372ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 373ad6eb31eSJames Morse 0, NULL); 374ad6eb31eSJames Morse } 375ad6eb31eSJames Morse 376f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 377f92b5462SJames Morse static void _local_event_enable(void *data) 378f92b5462SJames Morse { 379f92b5462SJames Morse int err; 380f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 381f92b5462SJames Morse 382f92b5462SJames Morse WARN_ON_ONCE(preemptible()); 383f92b5462SJames Morse 384f92b5462SJames Morse err = sdei_api_event_enable(arg->event->event_num); 385f92b5462SJames Morse 386f92b5462SJames Morse sdei_cross_call_return(arg, err); 387f92b5462SJames Morse } 388f92b5462SJames Morse 389ad6eb31eSJames Morse int sdei_event_enable(u32 event_num) 390ad6eb31eSJames Morse { 391ad6eb31eSJames Morse int err = -EINVAL; 392ad6eb31eSJames Morse struct sdei_event *event; 393ad6eb31eSJames Morse 394ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 395ad6eb31eSJames Morse event = sdei_event_find(event_num); 396ad6eb31eSJames Morse if (!event) { 397ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 398ad6eb31eSJames Morse return -ENOENT; 399ad6eb31eSJames Morse } 400ad6eb31eSJames Morse 401da351827SJames Morse 40254f529a6SJames Morse cpus_read_lock(); 403ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 404ad6eb31eSJames Morse err = sdei_api_event_enable(event->event_num); 405f92b5462SJames Morse else 406f92b5462SJames Morse err = sdei_do_cross_call(_local_event_enable, event); 40754f529a6SJames Morse 40854f529a6SJames Morse if (!err) { 40954f529a6SJames Morse spin_lock(&sdei_list_lock); 41054f529a6SJames Morse event->reenable = true; 41154f529a6SJames Morse spin_unlock(&sdei_list_lock); 41254f529a6SJames Morse } 41354f529a6SJames Morse cpus_read_unlock(); 414ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 415ad6eb31eSJames Morse 416ad6eb31eSJames Morse return err; 417ad6eb31eSJames Morse } 418ad6eb31eSJames Morse 419ad6eb31eSJames Morse static int sdei_api_event_disable(u32 event_num) 420ad6eb31eSJames Morse { 421ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 422ad6eb31eSJames Morse 0, 0, NULL); 423ad6eb31eSJames Morse } 424ad6eb31eSJames Morse 425f92b5462SJames Morse static void _ipi_event_disable(void *data) 426f92b5462SJames Morse { 427f92b5462SJames Morse int err; 428f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 429f92b5462SJames Morse 430f92b5462SJames Morse err = sdei_api_event_disable(arg->event->event_num); 431f92b5462SJames Morse 432f92b5462SJames Morse sdei_cross_call_return(arg, err); 433f92b5462SJames Morse } 434f92b5462SJames Morse 435ad6eb31eSJames Morse int sdei_event_disable(u32 event_num) 436ad6eb31eSJames Morse { 437ad6eb31eSJames Morse int err = -EINVAL; 438ad6eb31eSJames Morse struct sdei_event *event; 439ad6eb31eSJames Morse 440ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 441ad6eb31eSJames Morse event = sdei_event_find(event_num); 442ad6eb31eSJames Morse if (!event) { 443ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 444ad6eb31eSJames Morse return -ENOENT; 445ad6eb31eSJames Morse } 446ad6eb31eSJames Morse 447da351827SJames Morse spin_lock(&sdei_list_lock); 448da351827SJames Morse event->reenable = false; 449da351827SJames Morse spin_unlock(&sdei_list_lock); 450da351827SJames Morse 451ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 452ad6eb31eSJames Morse err = sdei_api_event_disable(event->event_num); 453f92b5462SJames Morse else 454f92b5462SJames Morse err = sdei_do_cross_call(_ipi_event_disable, event); 455ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 456ad6eb31eSJames Morse 457ad6eb31eSJames Morse return err; 458ad6eb31eSJames Morse } 459ad6eb31eSJames Morse 460ad6eb31eSJames Morse static int sdei_api_event_unregister(u32 event_num) 461ad6eb31eSJames Morse { 462ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0, 463ad6eb31eSJames Morse 0, 0, 0, NULL); 464ad6eb31eSJames Morse } 465ad6eb31eSJames Morse 466f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 467f92b5462SJames Morse static void _local_event_unregister(void *data) 468f92b5462SJames Morse { 469f92b5462SJames Morse int err; 470f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 471f92b5462SJames Morse 472f92b5462SJames Morse WARN_ON_ONCE(preemptible()); 473f92b5462SJames Morse 474f92b5462SJames Morse err = sdei_api_event_unregister(arg->event->event_num); 475f92b5462SJames Morse 476f92b5462SJames Morse sdei_cross_call_return(arg, err); 477f92b5462SJames Morse } 478f92b5462SJames Morse 479ad6eb31eSJames Morse static int _sdei_event_unregister(struct sdei_event *event) 480ad6eb31eSJames Morse { 481ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 482ad6eb31eSJames Morse 483ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 484ad6eb31eSJames Morse return sdei_api_event_unregister(event->event_num); 485ad6eb31eSJames Morse 486f92b5462SJames Morse return sdei_do_cross_call(_local_event_unregister, event); 487ad6eb31eSJames Morse } 488ad6eb31eSJames Morse 489ad6eb31eSJames Morse int sdei_event_unregister(u32 event_num) 490ad6eb31eSJames Morse { 491ad6eb31eSJames Morse int err; 492ad6eb31eSJames Morse struct sdei_event *event; 493ad6eb31eSJames Morse 494ad6eb31eSJames Morse WARN_ON(in_nmi()); 495ad6eb31eSJames Morse 496ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 497ad6eb31eSJames Morse event = sdei_event_find(event_num); 498ad6eb31eSJames Morse if (!event) { 499ad6eb31eSJames Morse pr_warn("Event %u not registered\n", event_num); 500ad6eb31eSJames Morse err = -ENOENT; 501b06146b6SGavin Shan goto unlock; 502ad6eb31eSJames Morse } 503ad6eb31eSJames Morse 5046ded0b61SJames Morse spin_lock(&sdei_list_lock); 5056ded0b61SJames Morse event->reregister = false; 5066ded0b61SJames Morse event->reenable = false; 5076ded0b61SJames Morse spin_unlock(&sdei_list_lock); 5086ded0b61SJames Morse 509ad6eb31eSJames Morse err = _sdei_event_unregister(event); 510ad6eb31eSJames Morse if (err) 511b06146b6SGavin Shan goto unlock; 512ad6eb31eSJames Morse 513ad6eb31eSJames Morse sdei_event_destroy(event); 514b06146b6SGavin Shan unlock: 515ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 516ad6eb31eSJames Morse 517ad6eb31eSJames Morse return err; 518ad6eb31eSJames Morse } 519ad6eb31eSJames Morse 520da351827SJames Morse /* 521da351827SJames Morse * unregister events, but don't destroy them as they are re-registered by 522da351827SJames Morse * sdei_reregister_shared(). 523da351827SJames Morse */ 524da351827SJames Morse static int sdei_unregister_shared(void) 525da351827SJames Morse { 526da351827SJames Morse int err = 0; 527da351827SJames Morse struct sdei_event *event; 528da351827SJames Morse 529da351827SJames Morse mutex_lock(&sdei_events_lock); 530da351827SJames Morse spin_lock(&sdei_list_lock); 531da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 532da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 533da351827SJames Morse continue; 534da351827SJames Morse 535da351827SJames Morse err = _sdei_event_unregister(event); 536da351827SJames Morse if (err) 537da351827SJames Morse break; 538da351827SJames Morse } 539da351827SJames Morse spin_unlock(&sdei_list_lock); 540da351827SJames Morse mutex_unlock(&sdei_events_lock); 541da351827SJames Morse 542da351827SJames Morse return err; 543da351827SJames Morse } 544da351827SJames Morse 545ad6eb31eSJames Morse static int sdei_api_event_register(u32 event_num, unsigned long entry_point, 546ad6eb31eSJames Morse void *arg, u64 flags, u64 affinity) 547ad6eb31eSJames Morse { 548ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num, 549ad6eb31eSJames Morse (unsigned long)entry_point, (unsigned long)arg, 550ad6eb31eSJames Morse flags, affinity, NULL); 551ad6eb31eSJames Morse } 552ad6eb31eSJames Morse 553f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 554f92b5462SJames Morse static void _local_event_register(void *data) 555f92b5462SJames Morse { 556f92b5462SJames Morse int err; 557f92b5462SJames Morse struct sdei_registered_event *reg; 558f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 559f92b5462SJames Morse 560f92b5462SJames Morse WARN_ON(preemptible()); 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 static int _sdei_event_register(struct sdei_event *event) 570ad6eb31eSJames Morse { 571f92b5462SJames Morse int err; 572f92b5462SJames Morse 573ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 574ad6eb31eSJames Morse 575ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 576ad6eb31eSJames Morse return sdei_api_event_register(event->event_num, 577ad6eb31eSJames Morse sdei_entry_point, 578ad6eb31eSJames Morse event->registered, 579ad6eb31eSJames Morse SDEI_EVENT_REGISTER_RM_ANY, 0); 580ad6eb31eSJames Morse 581f92b5462SJames Morse err = sdei_do_cross_call(_local_event_register, event); 5826ded0b61SJames Morse if (err) 583f92b5462SJames Morse sdei_do_cross_call(_local_event_unregister, event); 584f92b5462SJames Morse 585f92b5462SJames Morse return err; 586ad6eb31eSJames Morse } 587ad6eb31eSJames Morse 588ad6eb31eSJames Morse int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) 589ad6eb31eSJames Morse { 590ad6eb31eSJames Morse int err; 591ad6eb31eSJames Morse struct sdei_event *event; 592ad6eb31eSJames Morse 593ad6eb31eSJames Morse WARN_ON(in_nmi()); 594ad6eb31eSJames Morse 595ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 596ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 597ad6eb31eSJames Morse pr_warn("Event %u already registered\n", event_num); 598ad6eb31eSJames Morse err = -EBUSY; 5991bbc7551SGavin Shan goto unlock; 600ad6eb31eSJames Morse } 601ad6eb31eSJames Morse 602ad6eb31eSJames Morse event = sdei_event_create(event_num, cb, arg); 603ad6eb31eSJames Morse if (IS_ERR(event)) { 604ad6eb31eSJames Morse err = PTR_ERR(event); 6051bbc7551SGavin Shan pr_warn("Failed to create event %u: %d\n", event_num, err); 6061bbc7551SGavin Shan goto unlock; 607ad6eb31eSJames Morse } 608ad6eb31eSJames Morse 60954f529a6SJames Morse cpus_read_lock(); 610ad6eb31eSJames Morse err = _sdei_event_register(event); 611ad6eb31eSJames Morse if (err) { 612ad6eb31eSJames Morse sdei_event_destroy(event); 6131bbc7551SGavin Shan pr_warn("Failed to register event %u: %d\n", event_num, err); 6141bbc7551SGavin Shan goto cpu_unlock; 6151bbc7551SGavin Shan } 6161bbc7551SGavin Shan 61754f529a6SJames Morse spin_lock(&sdei_list_lock); 61854f529a6SJames Morse event->reregister = true; 61954f529a6SJames Morse spin_unlock(&sdei_list_lock); 6201bbc7551SGavin Shan cpu_unlock: 62154f529a6SJames Morse cpus_read_unlock(); 6221bbc7551SGavin Shan unlock: 623ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 624ad6eb31eSJames Morse return err; 625ad6eb31eSJames Morse } 626ad6eb31eSJames Morse 627c66d52b1SLiguang Zhang static int sdei_reregister_event_llocked(struct sdei_event *event) 628da351827SJames Morse { 629da351827SJames Morse int err; 630da351827SJames Morse 631da351827SJames Morse lockdep_assert_held(&sdei_events_lock); 632c66d52b1SLiguang Zhang lockdep_assert_held(&sdei_list_lock); 633da351827SJames Morse 634da351827SJames Morse err = _sdei_event_register(event); 635da351827SJames Morse if (err) { 636da351827SJames Morse pr_err("Failed to re-register event %u\n", event->event_num); 637c66d52b1SLiguang Zhang sdei_event_destroy_llocked(event); 638da351827SJames Morse return err; 639da351827SJames Morse } 640da351827SJames Morse 641da351827SJames Morse if (event->reenable) { 642da351827SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 643da351827SJames Morse err = sdei_api_event_enable(event->event_num); 644f92b5462SJames Morse else 645f92b5462SJames Morse err = sdei_do_cross_call(_local_event_enable, event); 646da351827SJames Morse } 647da351827SJames Morse 648da351827SJames Morse if (err) 649da351827SJames Morse pr_err("Failed to re-enable event %u\n", event->event_num); 650da351827SJames Morse 651da351827SJames Morse return err; 652da351827SJames Morse } 653da351827SJames Morse 654da351827SJames Morse static int sdei_reregister_shared(void) 655da351827SJames Morse { 656da351827SJames Morse int err = 0; 657da351827SJames Morse struct sdei_event *event; 658da351827SJames Morse 659da351827SJames Morse mutex_lock(&sdei_events_lock); 660da351827SJames Morse spin_lock(&sdei_list_lock); 661da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 662da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 663da351827SJames Morse continue; 664da351827SJames Morse 665da351827SJames Morse if (event->reregister) { 666c66d52b1SLiguang Zhang err = sdei_reregister_event_llocked(event); 667da351827SJames Morse if (err) 668da351827SJames Morse break; 669da351827SJames Morse } 670da351827SJames Morse } 671da351827SJames Morse spin_unlock(&sdei_list_lock); 672da351827SJames Morse mutex_unlock(&sdei_events_lock); 673da351827SJames Morse 674da351827SJames Morse return err; 675da351827SJames Morse } 676da351827SJames Morse 677f92b5462SJames Morse static int sdei_cpuhp_down(unsigned int cpu) 678f92b5462SJames Morse { 679f92b5462SJames Morse struct sdei_event *event; 680f92b5462SJames Morse struct sdei_crosscall_args arg; 681f92b5462SJames Morse 682f92b5462SJames Morse /* un-register private events */ 683f92b5462SJames Morse spin_lock(&sdei_list_lock); 684f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 685f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 686f92b5462SJames Morse continue; 687f92b5462SJames Morse 688f92b5462SJames Morse CROSSCALL_INIT(arg, event); 689f92b5462SJames Morse /* call the cross-call function locally... */ 690f92b5462SJames Morse _local_event_unregister(&arg); 691f92b5462SJames Morse if (arg.first_error) 692f92b5462SJames Morse pr_err("Failed to unregister event %u: %d\n", 693f92b5462SJames Morse event->event_num, arg.first_error); 694f92b5462SJames Morse } 695f92b5462SJames Morse spin_unlock(&sdei_list_lock); 696f92b5462SJames Morse 697f92b5462SJames Morse return sdei_mask_local_cpu(); 698f92b5462SJames Morse } 699f92b5462SJames Morse 700f92b5462SJames Morse static int sdei_cpuhp_up(unsigned int cpu) 701f92b5462SJames Morse { 702f92b5462SJames Morse struct sdei_event *event; 703f92b5462SJames Morse struct sdei_crosscall_args arg; 704f92b5462SJames Morse 705f92b5462SJames Morse /* re-register/enable private events */ 706f92b5462SJames Morse spin_lock(&sdei_list_lock); 707f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 708f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 709f92b5462SJames Morse continue; 710f92b5462SJames Morse 711f92b5462SJames Morse if (event->reregister) { 712f92b5462SJames Morse CROSSCALL_INIT(arg, event); 713f92b5462SJames Morse /* call the cross-call function locally... */ 714f92b5462SJames Morse _local_event_register(&arg); 715f92b5462SJames Morse if (arg.first_error) 716f92b5462SJames Morse pr_err("Failed to re-register event %u: %d\n", 717f92b5462SJames Morse event->event_num, arg.first_error); 718f92b5462SJames Morse } 719f92b5462SJames Morse 720f92b5462SJames Morse if (event->reenable) { 721f92b5462SJames Morse CROSSCALL_INIT(arg, event); 722f92b5462SJames Morse _local_event_enable(&arg); 723f92b5462SJames Morse if (arg.first_error) 724f92b5462SJames Morse pr_err("Failed to re-enable event %u: %d\n", 725f92b5462SJames Morse event->event_num, arg.first_error); 726f92b5462SJames Morse } 727f92b5462SJames Morse } 728f92b5462SJames Morse spin_unlock(&sdei_list_lock); 729f92b5462SJames Morse 730f92b5462SJames Morse return sdei_unmask_local_cpu(); 731f92b5462SJames Morse } 732f92b5462SJames Morse 733da351827SJames Morse /* When entering idle, mask/unmask events for this cpu */ 734da351827SJames Morse static int sdei_pm_notifier(struct notifier_block *nb, unsigned long action, 735da351827SJames Morse void *data) 736da351827SJames Morse { 737da351827SJames Morse int rv; 738da351827SJames Morse 739da351827SJames Morse switch (action) { 740da351827SJames Morse case CPU_PM_ENTER: 741da351827SJames Morse rv = sdei_mask_local_cpu(); 742da351827SJames Morse break; 743da351827SJames Morse case CPU_PM_EXIT: 744da351827SJames Morse case CPU_PM_ENTER_FAILED: 745da351827SJames Morse rv = sdei_unmask_local_cpu(); 746da351827SJames Morse break; 747da351827SJames Morse default: 748da351827SJames Morse return NOTIFY_DONE; 749da351827SJames Morse } 750da351827SJames Morse 751da351827SJames Morse if (rv) 752da351827SJames Morse return notifier_from_errno(rv); 753da351827SJames Morse 754da351827SJames Morse return NOTIFY_OK; 755da351827SJames Morse } 756da351827SJames Morse 757da351827SJames Morse static struct notifier_block sdei_pm_nb = { 758da351827SJames Morse .notifier_call = sdei_pm_notifier, 759da351827SJames Morse }; 760da351827SJames Morse 761da351827SJames Morse static int sdei_device_suspend(struct device *dev) 762da351827SJames Morse { 763da351827SJames Morse on_each_cpu(_ipi_mask_cpu, NULL, true); 764da351827SJames Morse 765da351827SJames Morse return 0; 766da351827SJames Morse } 767da351827SJames Morse 768da351827SJames Morse static int sdei_device_resume(struct device *dev) 769da351827SJames Morse { 770da351827SJames Morse on_each_cpu(_ipi_unmask_cpu, NULL, true); 771da351827SJames Morse 772da351827SJames Morse return 0; 773da351827SJames Morse } 774da351827SJames Morse 775da351827SJames Morse /* 776da351827SJames Morse * We need all events to be reregistered when we resume from hibernate. 777da351827SJames Morse * 778da351827SJames Morse * The sequence is freeze->thaw. Reboot. freeze->restore. We unregister 779da351827SJames Morse * events during freeze, then re-register and re-enable them during thaw 780da351827SJames Morse * and restore. 781da351827SJames Morse */ 782da351827SJames Morse static int sdei_device_freeze(struct device *dev) 783da351827SJames Morse { 784da351827SJames Morse int err; 785da351827SJames Morse 786f92b5462SJames Morse /* unregister private events */ 787da351827SJames Morse cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING); 788da351827SJames Morse 789da351827SJames Morse err = sdei_unregister_shared(); 790da351827SJames Morse if (err) 791da351827SJames Morse return err; 792da351827SJames Morse 793da351827SJames Morse return 0; 794da351827SJames Morse } 795da351827SJames Morse 796da351827SJames Morse static int sdei_device_thaw(struct device *dev) 797da351827SJames Morse { 798da351827SJames Morse int err; 799da351827SJames Morse 800da351827SJames Morse /* re-register shared events */ 801da351827SJames Morse err = sdei_reregister_shared(); 802da351827SJames Morse if (err) { 803da351827SJames Morse pr_warn("Failed to re-register shared events...\n"); 804da351827SJames Morse sdei_mark_interface_broken(); 805da351827SJames Morse return err; 806da351827SJames Morse } 807da351827SJames Morse 808da351827SJames Morse err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI", 809da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 810da351827SJames Morse if (err) 811da351827SJames Morse pr_warn("Failed to re-register CPU hotplug notifier...\n"); 812da351827SJames Morse 813da351827SJames Morse return err; 814da351827SJames Morse } 815da351827SJames Morse 816da351827SJames Morse static int sdei_device_restore(struct device *dev) 817da351827SJames Morse { 818da351827SJames Morse int err; 819da351827SJames Morse 820da351827SJames Morse err = sdei_platform_reset(); 821da351827SJames Morse if (err) 822da351827SJames Morse return err; 823da351827SJames Morse 824da351827SJames Morse return sdei_device_thaw(dev); 825da351827SJames Morse } 826da351827SJames Morse 827da351827SJames Morse static const struct dev_pm_ops sdei_pm_ops = { 828da351827SJames Morse .suspend = sdei_device_suspend, 829da351827SJames Morse .resume = sdei_device_resume, 830da351827SJames Morse .freeze = sdei_device_freeze, 831da351827SJames Morse .thaw = sdei_device_thaw, 832da351827SJames Morse .restore = sdei_device_restore, 833da351827SJames Morse }; 834da351827SJames Morse 835da351827SJames Morse /* 836da351827SJames Morse * Mask all CPUs and unregister all events on panic, reboot or kexec. 837da351827SJames Morse */ 838da351827SJames Morse static int sdei_reboot_notifier(struct notifier_block *nb, unsigned long action, 839da351827SJames Morse void *data) 840da351827SJames Morse { 841da351827SJames Morse /* 842da351827SJames Morse * We are going to reset the interface, after this there is no point 843da351827SJames Morse * doing work when we take CPUs offline. 844da351827SJames Morse */ 845da351827SJames Morse cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING); 846da351827SJames Morse 847da351827SJames Morse sdei_platform_reset(); 848da351827SJames Morse 849da351827SJames Morse return NOTIFY_OK; 850da351827SJames Morse } 851da351827SJames Morse 852da351827SJames Morse static struct notifier_block sdei_reboot_nb = { 853da351827SJames Morse .notifier_call = sdei_reboot_notifier, 854da351827SJames Morse }; 855da351827SJames Morse 856ad6eb31eSJames Morse static void sdei_smccc_smc(unsigned long function_id, 857ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 858ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 859ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 860ad6eb31eSJames Morse { 861ad6eb31eSJames Morse arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 862ad6eb31eSJames Morse } 8632f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_smc); 864ad6eb31eSJames Morse 865ad6eb31eSJames Morse static void sdei_smccc_hvc(unsigned long function_id, 866ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 867ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 868ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 869ad6eb31eSJames Morse { 870ad6eb31eSJames Morse arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 871ad6eb31eSJames Morse } 8722f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_hvc); 873ad6eb31eSJames Morse 874f96935d3SJames Morse int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, 875f96935d3SJames Morse sdei_event_callback *critical_cb) 876f96935d3SJames Morse { 877f96935d3SJames Morse int err; 878f96935d3SJames Morse u64 result; 879f96935d3SJames Morse u32 event_num; 880f96935d3SJames Morse sdei_event_callback *cb; 881f96935d3SJames Morse 882f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 883f96935d3SJames Morse return -EOPNOTSUPP; 884f96935d3SJames Morse 885f96935d3SJames Morse event_num = ghes->generic->notify.vector; 886f96935d3SJames Morse if (event_num == 0) { 887f96935d3SJames Morse /* 888f96935d3SJames Morse * Event 0 is reserved by the specification for 889f96935d3SJames Morse * SDEI_EVENT_SIGNAL. 890f96935d3SJames Morse */ 891f96935d3SJames Morse return -EINVAL; 892f96935d3SJames Morse } 893f96935d3SJames Morse 894f96935d3SJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 895f96935d3SJames Morse &result); 896f96935d3SJames Morse if (err) 897f96935d3SJames Morse return err; 898f96935d3SJames Morse 899f96935d3SJames Morse if (result == SDEI_EVENT_PRIORITY_CRITICAL) 900f96935d3SJames Morse cb = critical_cb; 901f96935d3SJames Morse else 902f96935d3SJames Morse cb = normal_cb; 903f96935d3SJames Morse 904f96935d3SJames Morse err = sdei_event_register(event_num, cb, ghes); 905f96935d3SJames Morse if (!err) 906f96935d3SJames Morse err = sdei_event_enable(event_num); 907f96935d3SJames Morse 908f96935d3SJames Morse return err; 909f96935d3SJames Morse } 910f96935d3SJames Morse 911f96935d3SJames Morse int sdei_unregister_ghes(struct ghes *ghes) 912f96935d3SJames Morse { 913f96935d3SJames Morse int i; 914f96935d3SJames Morse int err; 915f96935d3SJames Morse u32 event_num = ghes->generic->notify.vector; 916f96935d3SJames Morse 917f96935d3SJames Morse might_sleep(); 918f96935d3SJames Morse 919f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 920f96935d3SJames Morse return -EOPNOTSUPP; 921f96935d3SJames Morse 922f96935d3SJames Morse /* 923f96935d3SJames Morse * The event may be running on another CPU. Disable it 924f96935d3SJames Morse * to stop new events, then try to unregister a few times. 925f96935d3SJames Morse */ 926f96935d3SJames Morse err = sdei_event_disable(event_num); 927f96935d3SJames Morse if (err) 928f96935d3SJames Morse return err; 929f96935d3SJames Morse 930f96935d3SJames Morse for (i = 0; i < 3; i++) { 931f96935d3SJames Morse err = sdei_event_unregister(event_num); 932f96935d3SJames Morse if (err != -EINPROGRESS) 933f96935d3SJames Morse break; 934f96935d3SJames Morse 935f96935d3SJames Morse schedule(); 936f96935d3SJames Morse } 937f96935d3SJames Morse 938f96935d3SJames Morse return err; 939f96935d3SJames Morse } 940f96935d3SJames Morse 941ad6eb31eSJames Morse static int sdei_get_conduit(struct platform_device *pdev) 942ad6eb31eSJames Morse { 943ad6eb31eSJames Morse const char *method; 944ad6eb31eSJames Morse struct device_node *np = pdev->dev.of_node; 945ad6eb31eSJames Morse 946ad6eb31eSJames Morse sdei_firmware_call = NULL; 947ad6eb31eSJames Morse if (np) { 948ad6eb31eSJames Morse if (of_property_read_string(np, "method", &method)) { 949ad6eb31eSJames Morse pr_warn("missing \"method\" property\n"); 950e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 951ad6eb31eSJames Morse } 952ad6eb31eSJames Morse 953ad6eb31eSJames Morse if (!strcmp("hvc", method)) { 954ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 955e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 956ad6eb31eSJames Morse } else if (!strcmp("smc", method)) { 957ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_smc; 958e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 959ad6eb31eSJames Morse } 960ad6eb31eSJames Morse 961ad6eb31eSJames Morse pr_warn("invalid \"method\" property: %s\n", method); 962bc110fd3SGavin Shan } else if (!acpi_disabled) { 963677a60bdSJames Morse if (acpi_psci_use_hvc()) { 964677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 965e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 966677a60bdSJames Morse } else { 967677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_smc; 968e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 969677a60bdSJames Morse } 970ad6eb31eSJames Morse } 971ad6eb31eSJames Morse 972e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 973ad6eb31eSJames Morse } 974ad6eb31eSJames Morse 975ad6eb31eSJames Morse static int sdei_probe(struct platform_device *pdev) 976ad6eb31eSJames Morse { 977ad6eb31eSJames Morse int err; 978ad6eb31eSJames Morse u64 ver = 0; 979ad6eb31eSJames Morse int conduit; 980ad6eb31eSJames Morse 981ad6eb31eSJames Morse conduit = sdei_get_conduit(pdev); 982ad6eb31eSJames Morse if (!sdei_firmware_call) 983ad6eb31eSJames Morse return 0; 984ad6eb31eSJames Morse 985ad6eb31eSJames Morse err = sdei_api_get_version(&ver); 986ad6eb31eSJames Morse if (err) { 987ad6eb31eSJames Morse pr_err("Failed to get SDEI version: %d\n", err); 988ad6eb31eSJames Morse sdei_mark_interface_broken(); 989ad6eb31eSJames Morse return err; 990ad6eb31eSJames Morse } 991ad6eb31eSJames Morse 992ad6eb31eSJames Morse pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n", 993ad6eb31eSJames Morse (int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver), 994ad6eb31eSJames Morse (int)SDEI_VERSION_VENDOR(ver)); 995ad6eb31eSJames Morse 996ad6eb31eSJames Morse if (SDEI_VERSION_MAJOR(ver) != 1) { 997ad6eb31eSJames Morse pr_warn("Conflicting SDEI version detected.\n"); 998ad6eb31eSJames Morse sdei_mark_interface_broken(); 999ad6eb31eSJames Morse return -EINVAL; 1000ad6eb31eSJames Morse } 1001ad6eb31eSJames Morse 1002ad6eb31eSJames Morse err = sdei_platform_reset(); 1003ad6eb31eSJames Morse if (err) 1004ad6eb31eSJames Morse return err; 1005ad6eb31eSJames Morse 1006ad6eb31eSJames Morse sdei_entry_point = sdei_arch_get_entry_point(conduit); 1007ad6eb31eSJames Morse if (!sdei_entry_point) { 1008ad6eb31eSJames Morse /* Not supported due to hardware or boot configuration */ 1009ad6eb31eSJames Morse sdei_mark_interface_broken(); 1010ad6eb31eSJames Morse return 0; 1011ad6eb31eSJames Morse } 1012ad6eb31eSJames Morse 1013da351827SJames Morse err = cpu_pm_register_notifier(&sdei_pm_nb); 1014da351827SJames Morse if (err) { 1015da351827SJames Morse pr_warn("Failed to register CPU PM notifier...\n"); 1016da351827SJames Morse goto error; 1017da351827SJames Morse } 1018da351827SJames Morse 1019da351827SJames Morse err = register_reboot_notifier(&sdei_reboot_nb); 1020da351827SJames Morse if (err) { 1021da351827SJames Morse pr_warn("Failed to register reboot notifier...\n"); 1022da351827SJames Morse goto remove_cpupm; 1023da351827SJames Morse } 1024da351827SJames Morse 1025da351827SJames Morse err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI", 1026da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 1027da351827SJames Morse if (err) { 1028da351827SJames Morse pr_warn("Failed to register CPU hotplug notifier...\n"); 1029da351827SJames Morse goto remove_reboot; 1030da351827SJames Morse } 1031ad6eb31eSJames Morse 1032ad6eb31eSJames Morse return 0; 1033da351827SJames Morse 1034da351827SJames Morse remove_reboot: 1035da351827SJames Morse unregister_reboot_notifier(&sdei_reboot_nb); 1036da351827SJames Morse 1037da351827SJames Morse remove_cpupm: 1038da351827SJames Morse cpu_pm_unregister_notifier(&sdei_pm_nb); 1039da351827SJames Morse 1040da351827SJames Morse error: 1041da351827SJames Morse sdei_mark_interface_broken(); 1042da351827SJames Morse return err; 1043ad6eb31eSJames Morse } 1044ad6eb31eSJames Morse 1045ad6eb31eSJames Morse static const struct of_device_id sdei_of_match[] = { 1046ad6eb31eSJames Morse { .compatible = "arm,sdei-1.0" }, 1047ad6eb31eSJames Morse {} 1048ad6eb31eSJames Morse }; 1049ad6eb31eSJames Morse 1050ad6eb31eSJames Morse static struct platform_driver sdei_driver = { 1051ad6eb31eSJames Morse .driver = { 1052ad6eb31eSJames Morse .name = "sdei", 1053da351827SJames Morse .pm = &sdei_pm_ops, 1054ad6eb31eSJames Morse .of_match_table = sdei_of_match, 1055ad6eb31eSJames Morse }, 1056ad6eb31eSJames Morse .probe = sdei_probe, 1057ad6eb31eSJames Morse }; 1058ad6eb31eSJames Morse 1059677a60bdSJames Morse static bool __init sdei_present_acpi(void) 1060677a60bdSJames Morse { 1061677a60bdSJames Morse acpi_status status; 1062677a60bdSJames Morse struct acpi_table_header *sdei_table_header; 1063677a60bdSJames Morse 1064677a60bdSJames Morse if (acpi_disabled) 1065677a60bdSJames Morse return false; 1066677a60bdSJames Morse 1067677a60bdSJames Morse status = acpi_get_table(ACPI_SIG_SDEI, 0, &sdei_table_header); 1068677a60bdSJames Morse if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { 1069677a60bdSJames Morse const char *msg = acpi_format_exception(status); 1070677a60bdSJames Morse 1071677a60bdSJames Morse pr_info("Failed to get ACPI:SDEI table, %s\n", msg); 1072677a60bdSJames Morse } 1073677a60bdSJames Morse if (ACPI_FAILURE(status)) 1074677a60bdSJames Morse return false; 1075677a60bdSJames Morse 107670e6352aSHanjun Guo acpi_put_table(sdei_table_header); 107770e6352aSHanjun Guo 1078677a60bdSJames Morse return true; 1079677a60bdSJames Morse } 1080677a60bdSJames Morse 1081ad6eb31eSJames Morse static int __init sdei_init(void) 1082ad6eb31eSJames Morse { 1083caf2cd61SSudeep Holla struct platform_device *pdev; 108410fd7c42SGavin Shan int ret; 108510fd7c42SGavin Shan 108610fd7c42SGavin Shan ret = platform_driver_register(&sdei_driver); 108710fd7c42SGavin Shan if (ret || !sdei_present_acpi()) 108810fd7c42SGavin Shan return ret; 1089caf2cd61SSudeep Holla 1090caf2cd61SSudeep Holla pdev = platform_device_register_simple(sdei_driver.driver.name, 1091caf2cd61SSudeep Holla 0, NULL, 0); 109263627caeSGavin Shan if (IS_ERR(pdev)) { 109363627caeSGavin Shan ret = PTR_ERR(pdev); 109463627caeSGavin Shan platform_driver_unregister(&sdei_driver); 109563627caeSGavin Shan pr_info("Failed to register ACPI:SDEI platform device %d\n", 109663627caeSGavin Shan ret); 109763627caeSGavin Shan } 1098caf2cd61SSudeep Holla 1099caf2cd61SSudeep Holla return ret; 1100ad6eb31eSJames Morse } 1101ad6eb31eSJames Morse 1102677a60bdSJames Morse /* 1103677a60bdSJames Morse * On an ACPI system SDEI needs to be ready before HEST:GHES tries to register 1104677a60bdSJames Morse * its events. ACPI is initialised from a subsys_initcall(), GHES is initialised 1105677a60bdSJames Morse * by device_initcall(). We want to be called in the middle. 1106677a60bdSJames Morse */ 1107ad6eb31eSJames Morse subsys_initcall_sync(sdei_init); 1108ad6eb31eSJames Morse 1109ad6eb31eSJames Morse int sdei_event_handler(struct pt_regs *regs, 1110ad6eb31eSJames Morse struct sdei_registered_event *arg) 1111ad6eb31eSJames Morse { 1112ad6eb31eSJames Morse int err; 1113ad6eb31eSJames Morse mm_segment_t orig_addr_limit; 1114ad6eb31eSJames Morse u32 event_num = arg->event_num; 1115ad6eb31eSJames Morse 1116472de63bSJames Morse /* 1117472de63bSJames Morse * Save restore 'fs'. 1118472de63bSJames Morse * The architecture's entry code save/restores 'fs' when taking an 1119472de63bSJames Morse * exception from the kernel. This ensures addr_limit isn't inherited 1120472de63bSJames Morse * if you interrupted something that allowed the uaccess routines to 1121472de63bSJames Morse * access kernel memory. 1122472de63bSJames Morse * Do the same here because this doesn't come via the same entry code. 1123472de63bSJames Morse */ 11243d13f313SChristoph Hellwig orig_addr_limit = force_uaccess_begin(); 1125ad6eb31eSJames Morse 1126ad6eb31eSJames Morse err = arg->callback(event_num, regs, arg->callback_arg); 1127ad6eb31eSJames Morse if (err) 1128ad6eb31eSJames Morse pr_err_ratelimited("event %u on CPU %u failed with error: %d\n", 1129ad6eb31eSJames Morse event_num, smp_processor_id(), err); 1130ad6eb31eSJames Morse 11313d13f313SChristoph Hellwig force_uaccess_end(orig_addr_limit); 1132ad6eb31eSJames Morse 1133ad6eb31eSJames Morse return err; 1134ad6eb31eSJames Morse } 1135ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_event_handler); 1136