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 81f92b5462SJames Morse #define CROSSCALL_INIT(arg, event) (arg.event = event, \ 82f92b5462SJames Morse arg.first_error = 0, \ 83f92b5462SJames Morse atomic_set(&arg.errors, 0)) 84f92b5462SJames Morse 85f92b5462SJames Morse static inline int sdei_do_cross_call(void *fn, struct sdei_event * event) 86f92b5462SJames Morse { 87f92b5462SJames Morse struct sdei_crosscall_args arg; 88f92b5462SJames Morse 89f92b5462SJames Morse CROSSCALL_INIT(arg, event); 90f92b5462SJames Morse on_each_cpu(fn, &arg, true); 91f92b5462SJames Morse 92f92b5462SJames Morse return arg.first_error; 93f92b5462SJames Morse } 94f92b5462SJames Morse 95f92b5462SJames Morse static inline void 96f92b5462SJames Morse sdei_cross_call_return(struct sdei_crosscall_args *arg, int err) 97f92b5462SJames Morse { 98f92b5462SJames Morse if (err && (atomic_inc_return(&arg->errors) == 1)) 99f92b5462SJames Morse arg->first_error = err; 100f92b5462SJames Morse } 101f92b5462SJames Morse 102ad6eb31eSJames Morse static int sdei_to_linux_errno(unsigned long sdei_err) 103ad6eb31eSJames Morse { 104ad6eb31eSJames Morse switch (sdei_err) { 105ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 106ad6eb31eSJames Morse return -EOPNOTSUPP; 107ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 108ad6eb31eSJames Morse return -EINVAL; 109ad6eb31eSJames Morse case SDEI_DENIED: 110ad6eb31eSJames Morse return -EPERM; 111ad6eb31eSJames Morse case SDEI_PENDING: 112ad6eb31eSJames Morse return -EINPROGRESS; 113ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 114ad6eb31eSJames Morse return -ENOMEM; 115ad6eb31eSJames Morse } 116ad6eb31eSJames Morse 117ad6eb31eSJames Morse /* Not an error value ... */ 118ad6eb31eSJames Morse return sdei_err; 119ad6eb31eSJames Morse } 120ad6eb31eSJames Morse 121ad6eb31eSJames Morse /* 122ad6eb31eSJames Morse * If x0 is any of these values, then the call failed, use sdei_to_linux_errno() 123ad6eb31eSJames Morse * to translate. 124ad6eb31eSJames Morse */ 125ad6eb31eSJames Morse static int sdei_is_err(struct arm_smccc_res *res) 126ad6eb31eSJames Morse { 127ad6eb31eSJames Morse switch (res->a0) { 128ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 129ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 130ad6eb31eSJames Morse case SDEI_DENIED: 131ad6eb31eSJames Morse case SDEI_PENDING: 132ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 133ad6eb31eSJames Morse return true; 134ad6eb31eSJames Morse } 135ad6eb31eSJames Morse 136ad6eb31eSJames Morse return false; 137ad6eb31eSJames Morse } 138ad6eb31eSJames Morse 139ad6eb31eSJames Morse static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0, 140ad6eb31eSJames Morse unsigned long arg1, unsigned long arg2, 141ad6eb31eSJames Morse unsigned long arg3, unsigned long arg4, 142ad6eb31eSJames Morse u64 *result) 143ad6eb31eSJames Morse { 144ad6eb31eSJames Morse int err = 0; 145ad6eb31eSJames Morse struct arm_smccc_res res; 146ad6eb31eSJames Morse 147ad6eb31eSJames Morse if (sdei_firmware_call) { 148ad6eb31eSJames Morse sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4, 149ad6eb31eSJames Morse &res); 150ad6eb31eSJames Morse if (sdei_is_err(&res)) 151ad6eb31eSJames Morse err = sdei_to_linux_errno(res.a0); 152ad6eb31eSJames Morse } else { 153ad6eb31eSJames Morse /* 154ad6eb31eSJames Morse * !sdei_firmware_call means we failed to probe or called 155ad6eb31eSJames Morse * sdei_mark_interface_broken(). -EIO is not an error returned 156ad6eb31eSJames Morse * by sdei_to_linux_errno() and is used to suppress messages 157ad6eb31eSJames Morse * from this driver. 158ad6eb31eSJames Morse */ 159ad6eb31eSJames Morse err = -EIO; 160ad6eb31eSJames Morse res.a0 = SDEI_NOT_SUPPORTED; 161ad6eb31eSJames Morse } 162ad6eb31eSJames Morse 163ad6eb31eSJames Morse if (result) 164ad6eb31eSJames Morse *result = res.a0; 165ad6eb31eSJames Morse 166ad6eb31eSJames Morse return err; 167ad6eb31eSJames Morse } 1682f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(invoke_sdei_fn); 169ad6eb31eSJames Morse 170ad6eb31eSJames Morse static struct sdei_event *sdei_event_find(u32 event_num) 171ad6eb31eSJames Morse { 172ad6eb31eSJames Morse struct sdei_event *e, *found = NULL; 173ad6eb31eSJames Morse 174ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 175ad6eb31eSJames Morse 176ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 177ad6eb31eSJames Morse list_for_each_entry(e, &sdei_list, list) { 178ad6eb31eSJames Morse if (e->event_num == event_num) { 179ad6eb31eSJames Morse found = e; 180ad6eb31eSJames Morse break; 181ad6eb31eSJames Morse } 182ad6eb31eSJames Morse } 183ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 184ad6eb31eSJames Morse 185ad6eb31eSJames Morse return found; 186ad6eb31eSJames Morse } 187ad6eb31eSJames Morse 188ad6eb31eSJames Morse int sdei_api_event_context(u32 query, u64 *result) 189ad6eb31eSJames Morse { 190ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0, 191ad6eb31eSJames Morse result); 192ad6eb31eSJames Morse } 193ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_api_event_context); 194ad6eb31eSJames Morse 195ad6eb31eSJames Morse static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) 196ad6eb31eSJames Morse { 197ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, 198ad6eb31eSJames Morse 0, 0, result); 199ad6eb31eSJames Morse } 200ad6eb31eSJames Morse 201ad6eb31eSJames Morse static struct sdei_event *sdei_event_create(u32 event_num, 202ad6eb31eSJames Morse sdei_event_callback *cb, 203ad6eb31eSJames Morse void *cb_arg) 204ad6eb31eSJames Morse { 205ad6eb31eSJames Morse int err; 206ad6eb31eSJames Morse u64 result; 207ad6eb31eSJames Morse struct sdei_event *event; 208ad6eb31eSJames Morse struct sdei_registered_event *reg; 209ad6eb31eSJames Morse 210ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 211ad6eb31eSJames Morse 212ad6eb31eSJames Morse event = kzalloc(sizeof(*event), GFP_KERNEL); 213ad6eb31eSJames Morse if (!event) 214ad6eb31eSJames Morse return ERR_PTR(-ENOMEM); 215ad6eb31eSJames Morse 216ad6eb31eSJames Morse INIT_LIST_HEAD(&event->list); 217ad6eb31eSJames Morse event->event_num = event_num; 218ad6eb31eSJames Morse 219ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 220ad6eb31eSJames Morse &result); 221ad6eb31eSJames Morse if (err) { 222ad6eb31eSJames Morse kfree(event); 223ad6eb31eSJames Morse return ERR_PTR(err); 224ad6eb31eSJames Morse } 225ad6eb31eSJames Morse event->priority = result; 226ad6eb31eSJames Morse 227ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE, 228ad6eb31eSJames Morse &result); 229ad6eb31eSJames Morse if (err) { 230ad6eb31eSJames Morse kfree(event); 231ad6eb31eSJames Morse return ERR_PTR(err); 232ad6eb31eSJames Morse } 233ad6eb31eSJames Morse event->type = result; 234ad6eb31eSJames Morse 235ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) { 236ad6eb31eSJames Morse reg = kzalloc(sizeof(*reg), GFP_KERNEL); 237ad6eb31eSJames Morse if (!reg) { 238ad6eb31eSJames Morse kfree(event); 239ad6eb31eSJames Morse return ERR_PTR(-ENOMEM); 240ad6eb31eSJames Morse } 241ad6eb31eSJames Morse 242ad6eb31eSJames Morse reg->event_num = event_num; 243ad6eb31eSJames Morse reg->priority = event->priority; 244ad6eb31eSJames Morse 245ad6eb31eSJames Morse reg->callback = cb; 246ad6eb31eSJames Morse reg->callback_arg = cb_arg; 247ad6eb31eSJames Morse event->registered = reg; 248f92b5462SJames Morse } else { 249f92b5462SJames Morse int cpu; 250f92b5462SJames Morse struct sdei_registered_event __percpu *regs; 251f92b5462SJames Morse 252f92b5462SJames Morse regs = alloc_percpu(struct sdei_registered_event); 253f92b5462SJames Morse if (!regs) { 254f92b5462SJames Morse kfree(event); 255f92b5462SJames Morse return ERR_PTR(-ENOMEM); 256f92b5462SJames Morse } 257f92b5462SJames Morse 258f92b5462SJames Morse for_each_possible_cpu(cpu) { 259f92b5462SJames Morse reg = per_cpu_ptr(regs, cpu); 260f92b5462SJames Morse 261f92b5462SJames Morse reg->event_num = event->event_num; 262f92b5462SJames Morse reg->priority = event->priority; 263f92b5462SJames Morse reg->callback = cb; 264f92b5462SJames Morse reg->callback_arg = cb_arg; 265f92b5462SJames Morse } 266f92b5462SJames Morse 267f92b5462SJames Morse event->private_registered = regs; 268ad6eb31eSJames Morse } 269ad6eb31eSJames Morse 270ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 271ad6eb31eSJames Morse kfree(event->registered); 272ad6eb31eSJames Morse kfree(event); 273ad6eb31eSJames Morse event = ERR_PTR(-EBUSY); 274ad6eb31eSJames Morse } else { 275ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 276ad6eb31eSJames Morse list_add(&event->list, &sdei_list); 277ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 278ad6eb31eSJames Morse } 279ad6eb31eSJames Morse 280ad6eb31eSJames Morse return event; 281ad6eb31eSJames Morse } 282ad6eb31eSJames Morse 283c66d52b1SLiguang Zhang static void sdei_event_destroy_llocked(struct sdei_event *event) 284ad6eb31eSJames Morse { 285ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 286c66d52b1SLiguang Zhang lockdep_assert_held(&sdei_list_lock); 287ad6eb31eSJames Morse 288ad6eb31eSJames Morse list_del(&event->list); 289ad6eb31eSJames Morse 290ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 291ad6eb31eSJames Morse kfree(event->registered); 292f92b5462SJames Morse else 293f92b5462SJames Morse free_percpu(event->private_registered); 294ad6eb31eSJames Morse 295ad6eb31eSJames Morse kfree(event); 296ad6eb31eSJames Morse } 297ad6eb31eSJames Morse 298c66d52b1SLiguang Zhang static void sdei_event_destroy(struct sdei_event *event) 299c66d52b1SLiguang Zhang { 300c66d52b1SLiguang Zhang spin_lock(&sdei_list_lock); 301c66d52b1SLiguang Zhang sdei_event_destroy_llocked(event); 302c66d52b1SLiguang Zhang spin_unlock(&sdei_list_lock); 303c66d52b1SLiguang Zhang } 304c66d52b1SLiguang Zhang 305ad6eb31eSJames Morse static int sdei_api_get_version(u64 *version) 306ad6eb31eSJames Morse { 307ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); 308ad6eb31eSJames Morse } 309ad6eb31eSJames Morse 310ad6eb31eSJames Morse int sdei_mask_local_cpu(void) 311ad6eb31eSJames Morse { 312ad6eb31eSJames Morse int err; 313ad6eb31eSJames Morse 314ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 315ad6eb31eSJames Morse 316ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL); 317ad6eb31eSJames Morse if (err && err != -EIO) { 318ad6eb31eSJames Morse pr_warn_once("failed to mask CPU[%u]: %d\n", 319ad6eb31eSJames Morse smp_processor_id(), err); 320ad6eb31eSJames Morse return err; 321ad6eb31eSJames Morse } 322ad6eb31eSJames Morse 323ad6eb31eSJames Morse return 0; 324ad6eb31eSJames Morse } 325ad6eb31eSJames Morse 326ad6eb31eSJames Morse static void _ipi_mask_cpu(void *ignored) 327ad6eb31eSJames Morse { 328ad6eb31eSJames Morse sdei_mask_local_cpu(); 329ad6eb31eSJames Morse } 330ad6eb31eSJames Morse 331ad6eb31eSJames Morse int sdei_unmask_local_cpu(void) 332ad6eb31eSJames Morse { 333ad6eb31eSJames Morse int err; 334ad6eb31eSJames Morse 335ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 336ad6eb31eSJames Morse 337ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL); 338ad6eb31eSJames Morse if (err && err != -EIO) { 339ad6eb31eSJames Morse pr_warn_once("failed to unmask CPU[%u]: %d\n", 340ad6eb31eSJames Morse smp_processor_id(), err); 341ad6eb31eSJames Morse return err; 342ad6eb31eSJames Morse } 343ad6eb31eSJames Morse 344ad6eb31eSJames Morse return 0; 345ad6eb31eSJames Morse } 346ad6eb31eSJames Morse 347ad6eb31eSJames Morse static void _ipi_unmask_cpu(void *ignored) 348ad6eb31eSJames Morse { 349ad6eb31eSJames Morse sdei_unmask_local_cpu(); 350ad6eb31eSJames Morse } 351ad6eb31eSJames Morse 352ad6eb31eSJames Morse static void _ipi_private_reset(void *ignored) 353ad6eb31eSJames Morse { 354ad6eb31eSJames Morse int err; 355ad6eb31eSJames Morse 356ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0, 357ad6eb31eSJames Morse NULL); 358ad6eb31eSJames Morse if (err && err != -EIO) 359ad6eb31eSJames Morse pr_warn_once("failed to reset CPU[%u]: %d\n", 360ad6eb31eSJames Morse smp_processor_id(), err); 361ad6eb31eSJames Morse } 362ad6eb31eSJames Morse 363ad6eb31eSJames Morse static int sdei_api_shared_reset(void) 364ad6eb31eSJames Morse { 365ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0, 366ad6eb31eSJames Morse NULL); 367ad6eb31eSJames Morse } 368ad6eb31eSJames Morse 369ad6eb31eSJames Morse static void sdei_mark_interface_broken(void) 370ad6eb31eSJames Morse { 371ad6eb31eSJames Morse pr_err("disabling SDEI firmware interface\n"); 372ad6eb31eSJames Morse on_each_cpu(&_ipi_mask_cpu, NULL, true); 373ad6eb31eSJames Morse sdei_firmware_call = NULL; 374ad6eb31eSJames Morse } 375ad6eb31eSJames Morse 376ad6eb31eSJames Morse static int sdei_platform_reset(void) 377ad6eb31eSJames Morse { 378ad6eb31eSJames Morse int err; 379ad6eb31eSJames Morse 380ad6eb31eSJames Morse on_each_cpu(&_ipi_private_reset, NULL, true); 381ad6eb31eSJames Morse err = sdei_api_shared_reset(); 382ad6eb31eSJames Morse if (err) { 383ad6eb31eSJames Morse pr_err("Failed to reset platform: %d\n", err); 384ad6eb31eSJames Morse sdei_mark_interface_broken(); 385ad6eb31eSJames Morse } 386ad6eb31eSJames Morse 387ad6eb31eSJames Morse return err; 388ad6eb31eSJames Morse } 389ad6eb31eSJames Morse 390ad6eb31eSJames Morse static int sdei_api_event_enable(u32 event_num) 391ad6eb31eSJames Morse { 392ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 393ad6eb31eSJames Morse 0, NULL); 394ad6eb31eSJames Morse } 395ad6eb31eSJames Morse 396f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 397f92b5462SJames Morse static void _local_event_enable(void *data) 398f92b5462SJames Morse { 399f92b5462SJames Morse int err; 400f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 401f92b5462SJames Morse 402f92b5462SJames Morse WARN_ON_ONCE(preemptible()); 403f92b5462SJames Morse 404f92b5462SJames Morse err = sdei_api_event_enable(arg->event->event_num); 405f92b5462SJames Morse 406f92b5462SJames Morse sdei_cross_call_return(arg, err); 407f92b5462SJames Morse } 408f92b5462SJames Morse 409ad6eb31eSJames Morse int sdei_event_enable(u32 event_num) 410ad6eb31eSJames Morse { 411ad6eb31eSJames Morse int err = -EINVAL; 412ad6eb31eSJames Morse struct sdei_event *event; 413ad6eb31eSJames Morse 414ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 415ad6eb31eSJames Morse event = sdei_event_find(event_num); 416ad6eb31eSJames Morse if (!event) { 417ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 418ad6eb31eSJames Morse return -ENOENT; 419ad6eb31eSJames Morse } 420ad6eb31eSJames Morse 421da351827SJames Morse 422*54f529a6SJames Morse cpus_read_lock(); 423ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 424ad6eb31eSJames Morse err = sdei_api_event_enable(event->event_num); 425f92b5462SJames Morse else 426f92b5462SJames Morse err = sdei_do_cross_call(_local_event_enable, event); 427*54f529a6SJames Morse 428*54f529a6SJames Morse if (!err) { 429*54f529a6SJames Morse spin_lock(&sdei_list_lock); 430*54f529a6SJames Morse event->reenable = true; 431*54f529a6SJames Morse spin_unlock(&sdei_list_lock); 432*54f529a6SJames Morse } 433*54f529a6SJames Morse cpus_read_unlock(); 434ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 435ad6eb31eSJames Morse 436ad6eb31eSJames Morse return err; 437ad6eb31eSJames Morse } 438ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_enable); 439ad6eb31eSJames Morse 440ad6eb31eSJames Morse static int sdei_api_event_disable(u32 event_num) 441ad6eb31eSJames Morse { 442ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 443ad6eb31eSJames Morse 0, 0, NULL); 444ad6eb31eSJames Morse } 445ad6eb31eSJames Morse 446f92b5462SJames Morse static void _ipi_event_disable(void *data) 447f92b5462SJames Morse { 448f92b5462SJames Morse int err; 449f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 450f92b5462SJames Morse 451f92b5462SJames Morse err = sdei_api_event_disable(arg->event->event_num); 452f92b5462SJames Morse 453f92b5462SJames Morse sdei_cross_call_return(arg, err); 454f92b5462SJames Morse } 455f92b5462SJames Morse 456ad6eb31eSJames Morse int sdei_event_disable(u32 event_num) 457ad6eb31eSJames Morse { 458ad6eb31eSJames Morse int err = -EINVAL; 459ad6eb31eSJames Morse struct sdei_event *event; 460ad6eb31eSJames Morse 461ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 462ad6eb31eSJames Morse event = sdei_event_find(event_num); 463ad6eb31eSJames Morse if (!event) { 464ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 465ad6eb31eSJames Morse return -ENOENT; 466ad6eb31eSJames Morse } 467ad6eb31eSJames Morse 468da351827SJames Morse spin_lock(&sdei_list_lock); 469da351827SJames Morse event->reenable = false; 470da351827SJames Morse spin_unlock(&sdei_list_lock); 471da351827SJames Morse 472ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 473ad6eb31eSJames Morse err = sdei_api_event_disable(event->event_num); 474f92b5462SJames Morse else 475f92b5462SJames Morse err = sdei_do_cross_call(_ipi_event_disable, event); 476ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 477ad6eb31eSJames Morse 478ad6eb31eSJames Morse return err; 479ad6eb31eSJames Morse } 480ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_disable); 481ad6eb31eSJames Morse 482ad6eb31eSJames Morse static int sdei_api_event_unregister(u32 event_num) 483ad6eb31eSJames Morse { 484ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0, 485ad6eb31eSJames Morse 0, 0, 0, NULL); 486ad6eb31eSJames Morse } 487ad6eb31eSJames Morse 488f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 489f92b5462SJames Morse static void _local_event_unregister(void *data) 490f92b5462SJames Morse { 491f92b5462SJames Morse int err; 492f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 493f92b5462SJames Morse 494f92b5462SJames Morse WARN_ON_ONCE(preemptible()); 495f92b5462SJames Morse 496f92b5462SJames Morse err = sdei_api_event_unregister(arg->event->event_num); 497f92b5462SJames Morse 498f92b5462SJames Morse sdei_cross_call_return(arg, err); 499f92b5462SJames Morse } 500f92b5462SJames Morse 501ad6eb31eSJames Morse static int _sdei_event_unregister(struct sdei_event *event) 502ad6eb31eSJames Morse { 503ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 504ad6eb31eSJames Morse 505ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 506ad6eb31eSJames Morse return sdei_api_event_unregister(event->event_num); 507ad6eb31eSJames Morse 508f92b5462SJames Morse return sdei_do_cross_call(_local_event_unregister, event); 509ad6eb31eSJames Morse } 510ad6eb31eSJames Morse 511ad6eb31eSJames Morse int sdei_event_unregister(u32 event_num) 512ad6eb31eSJames Morse { 513ad6eb31eSJames Morse int err; 514ad6eb31eSJames Morse struct sdei_event *event; 515ad6eb31eSJames Morse 516ad6eb31eSJames Morse WARN_ON(in_nmi()); 517ad6eb31eSJames Morse 518ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 519ad6eb31eSJames Morse event = sdei_event_find(event_num); 520ad6eb31eSJames Morse do { 521ad6eb31eSJames Morse if (!event) { 522ad6eb31eSJames Morse pr_warn("Event %u not registered\n", event_num); 523ad6eb31eSJames Morse err = -ENOENT; 524ad6eb31eSJames Morse break; 525ad6eb31eSJames Morse } 526ad6eb31eSJames Morse 5276ded0b61SJames Morse spin_lock(&sdei_list_lock); 5286ded0b61SJames Morse event->reregister = false; 5296ded0b61SJames Morse event->reenable = false; 5306ded0b61SJames Morse spin_unlock(&sdei_list_lock); 5316ded0b61SJames Morse 532ad6eb31eSJames Morse err = _sdei_event_unregister(event); 533ad6eb31eSJames Morse if (err) 534ad6eb31eSJames Morse break; 535ad6eb31eSJames Morse 536ad6eb31eSJames Morse sdei_event_destroy(event); 537ad6eb31eSJames Morse } while (0); 538ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 539ad6eb31eSJames Morse 540ad6eb31eSJames Morse return err; 541ad6eb31eSJames Morse } 542ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_unregister); 543ad6eb31eSJames Morse 544da351827SJames Morse /* 545da351827SJames Morse * unregister events, but don't destroy them as they are re-registered by 546da351827SJames Morse * sdei_reregister_shared(). 547da351827SJames Morse */ 548da351827SJames Morse static int sdei_unregister_shared(void) 549da351827SJames Morse { 550da351827SJames Morse int err = 0; 551da351827SJames Morse struct sdei_event *event; 552da351827SJames Morse 553da351827SJames Morse mutex_lock(&sdei_events_lock); 554da351827SJames Morse spin_lock(&sdei_list_lock); 555da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 556da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 557da351827SJames Morse continue; 558da351827SJames Morse 559da351827SJames Morse err = _sdei_event_unregister(event); 560da351827SJames Morse if (err) 561da351827SJames Morse break; 562da351827SJames Morse } 563da351827SJames Morse spin_unlock(&sdei_list_lock); 564da351827SJames Morse mutex_unlock(&sdei_events_lock); 565da351827SJames Morse 566da351827SJames Morse return err; 567da351827SJames Morse } 568da351827SJames Morse 569ad6eb31eSJames Morse static int sdei_api_event_register(u32 event_num, unsigned long entry_point, 570ad6eb31eSJames Morse void *arg, u64 flags, u64 affinity) 571ad6eb31eSJames Morse { 572ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num, 573ad6eb31eSJames Morse (unsigned long)entry_point, (unsigned long)arg, 574ad6eb31eSJames Morse flags, affinity, NULL); 575ad6eb31eSJames Morse } 576ad6eb31eSJames Morse 577f92b5462SJames Morse /* Called directly by the hotplug callbacks */ 578f92b5462SJames Morse static void _local_event_register(void *data) 579f92b5462SJames Morse { 580f92b5462SJames Morse int err; 581f92b5462SJames Morse struct sdei_registered_event *reg; 582f92b5462SJames Morse struct sdei_crosscall_args *arg = data; 583f92b5462SJames Morse 584f92b5462SJames Morse WARN_ON(preemptible()); 585f92b5462SJames Morse 586f92b5462SJames Morse reg = per_cpu_ptr(arg->event->private_registered, smp_processor_id()); 587f92b5462SJames Morse err = sdei_api_event_register(arg->event->event_num, sdei_entry_point, 588f92b5462SJames Morse reg, 0, 0); 589f92b5462SJames Morse 590f92b5462SJames Morse sdei_cross_call_return(arg, err); 591f92b5462SJames Morse } 592f92b5462SJames Morse 593ad6eb31eSJames Morse static int _sdei_event_register(struct sdei_event *event) 594ad6eb31eSJames Morse { 595f92b5462SJames Morse int err; 596f92b5462SJames Morse 597ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 598ad6eb31eSJames Morse 599ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 600ad6eb31eSJames Morse return sdei_api_event_register(event->event_num, 601ad6eb31eSJames Morse sdei_entry_point, 602ad6eb31eSJames Morse event->registered, 603ad6eb31eSJames Morse SDEI_EVENT_REGISTER_RM_ANY, 0); 604ad6eb31eSJames Morse 605f92b5462SJames Morse err = sdei_do_cross_call(_local_event_register, event); 6066ded0b61SJames Morse if (err) 607f92b5462SJames Morse sdei_do_cross_call(_local_event_unregister, event); 608f92b5462SJames Morse 609f92b5462SJames Morse return err; 610ad6eb31eSJames Morse } 611ad6eb31eSJames Morse 612ad6eb31eSJames Morse int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) 613ad6eb31eSJames Morse { 614ad6eb31eSJames Morse int err; 615ad6eb31eSJames Morse struct sdei_event *event; 616ad6eb31eSJames Morse 617ad6eb31eSJames Morse WARN_ON(in_nmi()); 618ad6eb31eSJames Morse 619ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 620ad6eb31eSJames Morse do { 621ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 622ad6eb31eSJames Morse pr_warn("Event %u already registered\n", event_num); 623ad6eb31eSJames Morse err = -EBUSY; 624ad6eb31eSJames Morse break; 625ad6eb31eSJames Morse } 626ad6eb31eSJames Morse 627ad6eb31eSJames Morse event = sdei_event_create(event_num, cb, arg); 628ad6eb31eSJames Morse if (IS_ERR(event)) { 629ad6eb31eSJames Morse err = PTR_ERR(event); 630ad6eb31eSJames Morse pr_warn("Failed to create event %u: %d\n", event_num, 631ad6eb31eSJames Morse err); 632ad6eb31eSJames Morse break; 633ad6eb31eSJames Morse } 634ad6eb31eSJames Morse 635*54f529a6SJames Morse cpus_read_lock(); 636ad6eb31eSJames Morse err = _sdei_event_register(event); 637ad6eb31eSJames Morse if (err) { 638ad6eb31eSJames Morse sdei_event_destroy(event); 639ad6eb31eSJames Morse pr_warn("Failed to register event %u: %d\n", event_num, 640ad6eb31eSJames Morse err); 641*54f529a6SJames Morse } else { 642*54f529a6SJames Morse spin_lock(&sdei_list_lock); 643*54f529a6SJames Morse event->reregister = true; 644*54f529a6SJames Morse spin_unlock(&sdei_list_lock); 645ad6eb31eSJames Morse } 646*54f529a6SJames Morse cpus_read_unlock(); 647ad6eb31eSJames Morse } while (0); 648ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 649ad6eb31eSJames Morse 650ad6eb31eSJames Morse return err; 651ad6eb31eSJames Morse } 652ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_register); 653ad6eb31eSJames Morse 654c66d52b1SLiguang Zhang static int sdei_reregister_event_llocked(struct sdei_event *event) 655da351827SJames Morse { 656da351827SJames Morse int err; 657da351827SJames Morse 658da351827SJames Morse lockdep_assert_held(&sdei_events_lock); 659c66d52b1SLiguang Zhang lockdep_assert_held(&sdei_list_lock); 660da351827SJames Morse 661da351827SJames Morse err = _sdei_event_register(event); 662da351827SJames Morse if (err) { 663da351827SJames Morse pr_err("Failed to re-register event %u\n", event->event_num); 664c66d52b1SLiguang Zhang sdei_event_destroy_llocked(event); 665da351827SJames Morse return err; 666da351827SJames Morse } 667da351827SJames Morse 668da351827SJames Morse if (event->reenable) { 669da351827SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 670da351827SJames Morse err = sdei_api_event_enable(event->event_num); 671f92b5462SJames Morse else 672f92b5462SJames Morse err = sdei_do_cross_call(_local_event_enable, event); 673da351827SJames Morse } 674da351827SJames Morse 675da351827SJames Morse if (err) 676da351827SJames Morse pr_err("Failed to re-enable event %u\n", event->event_num); 677da351827SJames Morse 678da351827SJames Morse return err; 679da351827SJames Morse } 680da351827SJames Morse 681da351827SJames Morse static int sdei_reregister_shared(void) 682da351827SJames Morse { 683da351827SJames Morse int err = 0; 684da351827SJames Morse struct sdei_event *event; 685da351827SJames Morse 686da351827SJames Morse mutex_lock(&sdei_events_lock); 687da351827SJames Morse spin_lock(&sdei_list_lock); 688da351827SJames Morse list_for_each_entry(event, &sdei_list, list) { 689da351827SJames Morse if (event->type != SDEI_EVENT_TYPE_SHARED) 690da351827SJames Morse continue; 691da351827SJames Morse 692da351827SJames Morse if (event->reregister) { 693c66d52b1SLiguang Zhang err = sdei_reregister_event_llocked(event); 694da351827SJames Morse if (err) 695da351827SJames Morse break; 696da351827SJames Morse } 697da351827SJames Morse } 698da351827SJames Morse spin_unlock(&sdei_list_lock); 699da351827SJames Morse mutex_unlock(&sdei_events_lock); 700da351827SJames Morse 701da351827SJames Morse return err; 702da351827SJames Morse } 703da351827SJames Morse 704f92b5462SJames Morse static int sdei_cpuhp_down(unsigned int cpu) 705f92b5462SJames Morse { 706f92b5462SJames Morse struct sdei_event *event; 707f92b5462SJames Morse struct sdei_crosscall_args arg; 708f92b5462SJames Morse 709f92b5462SJames Morse /* un-register private events */ 710f92b5462SJames Morse spin_lock(&sdei_list_lock); 711f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 712f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 713f92b5462SJames Morse continue; 714f92b5462SJames Morse 715f92b5462SJames Morse CROSSCALL_INIT(arg, event); 716f92b5462SJames Morse /* call the cross-call function locally... */ 717f92b5462SJames Morse _local_event_unregister(&arg); 718f92b5462SJames Morse if (arg.first_error) 719f92b5462SJames Morse pr_err("Failed to unregister event %u: %d\n", 720f92b5462SJames Morse event->event_num, arg.first_error); 721f92b5462SJames Morse } 722f92b5462SJames Morse spin_unlock(&sdei_list_lock); 723f92b5462SJames Morse 724f92b5462SJames Morse return sdei_mask_local_cpu(); 725f92b5462SJames Morse } 726f92b5462SJames Morse 727f92b5462SJames Morse static int sdei_cpuhp_up(unsigned int cpu) 728f92b5462SJames Morse { 729f92b5462SJames Morse struct sdei_event *event; 730f92b5462SJames Morse struct sdei_crosscall_args arg; 731f92b5462SJames Morse 732f92b5462SJames Morse /* re-register/enable private events */ 733f92b5462SJames Morse spin_lock(&sdei_list_lock); 734f92b5462SJames Morse list_for_each_entry(event, &sdei_list, list) { 735f92b5462SJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 736f92b5462SJames Morse continue; 737f92b5462SJames Morse 738f92b5462SJames Morse if (event->reregister) { 739f92b5462SJames Morse CROSSCALL_INIT(arg, event); 740f92b5462SJames Morse /* call the cross-call function locally... */ 741f92b5462SJames Morse _local_event_register(&arg); 742f92b5462SJames Morse if (arg.first_error) 743f92b5462SJames Morse pr_err("Failed to re-register event %u: %d\n", 744f92b5462SJames Morse event->event_num, arg.first_error); 745f92b5462SJames Morse } 746f92b5462SJames Morse 747f92b5462SJames Morse if (event->reenable) { 748f92b5462SJames Morse CROSSCALL_INIT(arg, event); 749f92b5462SJames Morse _local_event_enable(&arg); 750f92b5462SJames Morse if (arg.first_error) 751f92b5462SJames Morse pr_err("Failed to re-enable event %u: %d\n", 752f92b5462SJames Morse event->event_num, arg.first_error); 753f92b5462SJames Morse } 754f92b5462SJames Morse } 755f92b5462SJames Morse spin_unlock(&sdei_list_lock); 756f92b5462SJames Morse 757f92b5462SJames Morse return sdei_unmask_local_cpu(); 758f92b5462SJames Morse } 759f92b5462SJames Morse 760da351827SJames Morse /* When entering idle, mask/unmask events for this cpu */ 761da351827SJames Morse static int sdei_pm_notifier(struct notifier_block *nb, unsigned long action, 762da351827SJames Morse void *data) 763da351827SJames Morse { 764da351827SJames Morse int rv; 765da351827SJames Morse 766da351827SJames Morse switch (action) { 767da351827SJames Morse case CPU_PM_ENTER: 768da351827SJames Morse rv = sdei_mask_local_cpu(); 769da351827SJames Morse break; 770da351827SJames Morse case CPU_PM_EXIT: 771da351827SJames Morse case CPU_PM_ENTER_FAILED: 772da351827SJames Morse rv = sdei_unmask_local_cpu(); 773da351827SJames Morse break; 774da351827SJames Morse default: 775da351827SJames Morse return NOTIFY_DONE; 776da351827SJames Morse } 777da351827SJames Morse 778da351827SJames Morse if (rv) 779da351827SJames Morse return notifier_from_errno(rv); 780da351827SJames Morse 781da351827SJames Morse return NOTIFY_OK; 782da351827SJames Morse } 783da351827SJames Morse 784da351827SJames Morse static struct notifier_block sdei_pm_nb = { 785da351827SJames Morse .notifier_call = sdei_pm_notifier, 786da351827SJames Morse }; 787da351827SJames Morse 788da351827SJames Morse static int sdei_device_suspend(struct device *dev) 789da351827SJames Morse { 790da351827SJames Morse on_each_cpu(_ipi_mask_cpu, NULL, true); 791da351827SJames Morse 792da351827SJames Morse return 0; 793da351827SJames Morse } 794da351827SJames Morse 795da351827SJames Morse static int sdei_device_resume(struct device *dev) 796da351827SJames Morse { 797da351827SJames Morse on_each_cpu(_ipi_unmask_cpu, NULL, true); 798da351827SJames Morse 799da351827SJames Morse return 0; 800da351827SJames Morse } 801da351827SJames Morse 802da351827SJames Morse /* 803da351827SJames Morse * We need all events to be reregistered when we resume from hibernate. 804da351827SJames Morse * 805da351827SJames Morse * The sequence is freeze->thaw. Reboot. freeze->restore. We unregister 806da351827SJames Morse * events during freeze, then re-register and re-enable them during thaw 807da351827SJames Morse * and restore. 808da351827SJames Morse */ 809da351827SJames Morse static int sdei_device_freeze(struct device *dev) 810da351827SJames Morse { 811da351827SJames Morse int err; 812da351827SJames Morse 813f92b5462SJames Morse /* unregister private events */ 814da351827SJames Morse cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING); 815da351827SJames Morse 816da351827SJames Morse err = sdei_unregister_shared(); 817da351827SJames Morse if (err) 818da351827SJames Morse return err; 819da351827SJames Morse 820da351827SJames Morse return 0; 821da351827SJames Morse } 822da351827SJames Morse 823da351827SJames Morse static int sdei_device_thaw(struct device *dev) 824da351827SJames Morse { 825da351827SJames Morse int err; 826da351827SJames Morse 827da351827SJames Morse /* re-register shared events */ 828da351827SJames Morse err = sdei_reregister_shared(); 829da351827SJames Morse if (err) { 830da351827SJames Morse pr_warn("Failed to re-register shared events...\n"); 831da351827SJames Morse sdei_mark_interface_broken(); 832da351827SJames Morse return err; 833da351827SJames Morse } 834da351827SJames Morse 835da351827SJames Morse err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI", 836da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 837da351827SJames Morse if (err) 838da351827SJames Morse pr_warn("Failed to re-register CPU hotplug notifier...\n"); 839da351827SJames Morse 840da351827SJames Morse return err; 841da351827SJames Morse } 842da351827SJames Morse 843da351827SJames Morse static int sdei_device_restore(struct device *dev) 844da351827SJames Morse { 845da351827SJames Morse int err; 846da351827SJames Morse 847da351827SJames Morse err = sdei_platform_reset(); 848da351827SJames Morse if (err) 849da351827SJames Morse return err; 850da351827SJames Morse 851da351827SJames Morse return sdei_device_thaw(dev); 852da351827SJames Morse } 853da351827SJames Morse 854da351827SJames Morse static const struct dev_pm_ops sdei_pm_ops = { 855da351827SJames Morse .suspend = sdei_device_suspend, 856da351827SJames Morse .resume = sdei_device_resume, 857da351827SJames Morse .freeze = sdei_device_freeze, 858da351827SJames Morse .thaw = sdei_device_thaw, 859da351827SJames Morse .restore = sdei_device_restore, 860da351827SJames Morse }; 861da351827SJames Morse 862da351827SJames Morse /* 863da351827SJames Morse * Mask all CPUs and unregister all events on panic, reboot or kexec. 864da351827SJames Morse */ 865da351827SJames Morse static int sdei_reboot_notifier(struct notifier_block *nb, unsigned long action, 866da351827SJames Morse void *data) 867da351827SJames Morse { 868da351827SJames Morse /* 869da351827SJames Morse * We are going to reset the interface, after this there is no point 870da351827SJames Morse * doing work when we take CPUs offline. 871da351827SJames Morse */ 872da351827SJames Morse cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING); 873da351827SJames Morse 874da351827SJames Morse sdei_platform_reset(); 875da351827SJames Morse 876da351827SJames Morse return NOTIFY_OK; 877da351827SJames Morse } 878da351827SJames Morse 879da351827SJames Morse static struct notifier_block sdei_reboot_nb = { 880da351827SJames Morse .notifier_call = sdei_reboot_notifier, 881da351827SJames Morse }; 882da351827SJames Morse 883ad6eb31eSJames Morse static void sdei_smccc_smc(unsigned long function_id, 884ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 885ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 886ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 887ad6eb31eSJames Morse { 888ad6eb31eSJames Morse arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 889ad6eb31eSJames Morse } 8902f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_smc); 891ad6eb31eSJames Morse 892ad6eb31eSJames Morse static void sdei_smccc_hvc(unsigned long function_id, 893ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 894ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 895ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 896ad6eb31eSJames Morse { 897ad6eb31eSJames Morse arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 898ad6eb31eSJames Morse } 8992f1d4e24SXiongfeng Wang NOKPROBE_SYMBOL(sdei_smccc_hvc); 900ad6eb31eSJames Morse 901f96935d3SJames Morse int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, 902f96935d3SJames Morse sdei_event_callback *critical_cb) 903f96935d3SJames Morse { 904f96935d3SJames Morse int err; 905f96935d3SJames Morse u64 result; 906f96935d3SJames Morse u32 event_num; 907f96935d3SJames Morse sdei_event_callback *cb; 908f96935d3SJames Morse 909f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 910f96935d3SJames Morse return -EOPNOTSUPP; 911f96935d3SJames Morse 912f96935d3SJames Morse event_num = ghes->generic->notify.vector; 913f96935d3SJames Morse if (event_num == 0) { 914f96935d3SJames Morse /* 915f96935d3SJames Morse * Event 0 is reserved by the specification for 916f96935d3SJames Morse * SDEI_EVENT_SIGNAL. 917f96935d3SJames Morse */ 918f96935d3SJames Morse return -EINVAL; 919f96935d3SJames Morse } 920f96935d3SJames Morse 921f96935d3SJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 922f96935d3SJames Morse &result); 923f96935d3SJames Morse if (err) 924f96935d3SJames Morse return err; 925f96935d3SJames Morse 926f96935d3SJames Morse if (result == SDEI_EVENT_PRIORITY_CRITICAL) 927f96935d3SJames Morse cb = critical_cb; 928f96935d3SJames Morse else 929f96935d3SJames Morse cb = normal_cb; 930f96935d3SJames Morse 931f96935d3SJames Morse err = sdei_event_register(event_num, cb, ghes); 932f96935d3SJames Morse if (!err) 933f96935d3SJames Morse err = sdei_event_enable(event_num); 934f96935d3SJames Morse 935f96935d3SJames Morse return err; 936f96935d3SJames Morse } 937f96935d3SJames Morse 938f96935d3SJames Morse int sdei_unregister_ghes(struct ghes *ghes) 939f96935d3SJames Morse { 940f96935d3SJames Morse int i; 941f96935d3SJames Morse int err; 942f96935d3SJames Morse u32 event_num = ghes->generic->notify.vector; 943f96935d3SJames Morse 944f96935d3SJames Morse might_sleep(); 945f96935d3SJames Morse 946f96935d3SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 947f96935d3SJames Morse return -EOPNOTSUPP; 948f96935d3SJames Morse 949f96935d3SJames Morse /* 950f96935d3SJames Morse * The event may be running on another CPU. Disable it 951f96935d3SJames Morse * to stop new events, then try to unregister a few times. 952f96935d3SJames Morse */ 953f96935d3SJames Morse err = sdei_event_disable(event_num); 954f96935d3SJames Morse if (err) 955f96935d3SJames Morse return err; 956f96935d3SJames Morse 957f96935d3SJames Morse for (i = 0; i < 3; i++) { 958f96935d3SJames Morse err = sdei_event_unregister(event_num); 959f96935d3SJames Morse if (err != -EINPROGRESS) 960f96935d3SJames Morse break; 961f96935d3SJames Morse 962f96935d3SJames Morse schedule(); 963f96935d3SJames Morse } 964f96935d3SJames Morse 965f96935d3SJames Morse return err; 966f96935d3SJames Morse } 967f96935d3SJames Morse 968ad6eb31eSJames Morse static int sdei_get_conduit(struct platform_device *pdev) 969ad6eb31eSJames Morse { 970ad6eb31eSJames Morse const char *method; 971ad6eb31eSJames Morse struct device_node *np = pdev->dev.of_node; 972ad6eb31eSJames Morse 973ad6eb31eSJames Morse sdei_firmware_call = NULL; 974ad6eb31eSJames Morse if (np) { 975ad6eb31eSJames Morse if (of_property_read_string(np, "method", &method)) { 976ad6eb31eSJames Morse pr_warn("missing \"method\" property\n"); 977e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 978ad6eb31eSJames Morse } 979ad6eb31eSJames Morse 980ad6eb31eSJames Morse if (!strcmp("hvc", method)) { 981ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 982e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 983ad6eb31eSJames Morse } else if (!strcmp("smc", method)) { 984ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_smc; 985e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 986ad6eb31eSJames Morse } 987ad6eb31eSJames Morse 988ad6eb31eSJames Morse pr_warn("invalid \"method\" property: %s\n", method); 989677a60bdSJames Morse } else if (IS_ENABLED(CONFIG_ACPI) && !acpi_disabled) { 990677a60bdSJames Morse if (acpi_psci_use_hvc()) { 991677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 992e6ea4651SMark Rutland return SMCCC_CONDUIT_HVC; 993677a60bdSJames Morse } else { 994677a60bdSJames Morse sdei_firmware_call = &sdei_smccc_smc; 995e6ea4651SMark Rutland return SMCCC_CONDUIT_SMC; 996677a60bdSJames Morse } 997ad6eb31eSJames Morse } 998ad6eb31eSJames Morse 999e6ea4651SMark Rutland return SMCCC_CONDUIT_NONE; 1000ad6eb31eSJames Morse } 1001ad6eb31eSJames Morse 1002ad6eb31eSJames Morse static int sdei_probe(struct platform_device *pdev) 1003ad6eb31eSJames Morse { 1004ad6eb31eSJames Morse int err; 1005ad6eb31eSJames Morse u64 ver = 0; 1006ad6eb31eSJames Morse int conduit; 1007ad6eb31eSJames Morse 1008ad6eb31eSJames Morse conduit = sdei_get_conduit(pdev); 1009ad6eb31eSJames Morse if (!sdei_firmware_call) 1010ad6eb31eSJames Morse return 0; 1011ad6eb31eSJames Morse 1012ad6eb31eSJames Morse err = sdei_api_get_version(&ver); 1013ad6eb31eSJames Morse if (err == -EOPNOTSUPP) 1014ad6eb31eSJames Morse pr_err("advertised but not implemented in platform firmware\n"); 1015ad6eb31eSJames Morse if (err) { 1016ad6eb31eSJames Morse pr_err("Failed to get SDEI version: %d\n", err); 1017ad6eb31eSJames Morse sdei_mark_interface_broken(); 1018ad6eb31eSJames Morse return err; 1019ad6eb31eSJames Morse } 1020ad6eb31eSJames Morse 1021ad6eb31eSJames Morse pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n", 1022ad6eb31eSJames Morse (int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver), 1023ad6eb31eSJames Morse (int)SDEI_VERSION_VENDOR(ver)); 1024ad6eb31eSJames Morse 1025ad6eb31eSJames Morse if (SDEI_VERSION_MAJOR(ver) != 1) { 1026ad6eb31eSJames Morse pr_warn("Conflicting SDEI version detected.\n"); 1027ad6eb31eSJames Morse sdei_mark_interface_broken(); 1028ad6eb31eSJames Morse return -EINVAL; 1029ad6eb31eSJames Morse } 1030ad6eb31eSJames Morse 1031ad6eb31eSJames Morse err = sdei_platform_reset(); 1032ad6eb31eSJames Morse if (err) 1033ad6eb31eSJames Morse return err; 1034ad6eb31eSJames Morse 1035ad6eb31eSJames Morse sdei_entry_point = sdei_arch_get_entry_point(conduit); 1036ad6eb31eSJames Morse if (!sdei_entry_point) { 1037ad6eb31eSJames Morse /* Not supported due to hardware or boot configuration */ 1038ad6eb31eSJames Morse sdei_mark_interface_broken(); 1039ad6eb31eSJames Morse return 0; 1040ad6eb31eSJames Morse } 1041ad6eb31eSJames Morse 1042da351827SJames Morse err = cpu_pm_register_notifier(&sdei_pm_nb); 1043da351827SJames Morse if (err) { 1044da351827SJames Morse pr_warn("Failed to register CPU PM notifier...\n"); 1045da351827SJames Morse goto error; 1046da351827SJames Morse } 1047da351827SJames Morse 1048da351827SJames Morse err = register_reboot_notifier(&sdei_reboot_nb); 1049da351827SJames Morse if (err) { 1050da351827SJames Morse pr_warn("Failed to register reboot notifier...\n"); 1051da351827SJames Morse goto remove_cpupm; 1052da351827SJames Morse } 1053da351827SJames Morse 1054da351827SJames Morse err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI", 1055da351827SJames Morse &sdei_cpuhp_up, &sdei_cpuhp_down); 1056da351827SJames Morse if (err) { 1057da351827SJames Morse pr_warn("Failed to register CPU hotplug notifier...\n"); 1058da351827SJames Morse goto remove_reboot; 1059da351827SJames Morse } 1060ad6eb31eSJames Morse 1061ad6eb31eSJames Morse return 0; 1062da351827SJames Morse 1063da351827SJames Morse remove_reboot: 1064da351827SJames Morse unregister_reboot_notifier(&sdei_reboot_nb); 1065da351827SJames Morse 1066da351827SJames Morse remove_cpupm: 1067da351827SJames Morse cpu_pm_unregister_notifier(&sdei_pm_nb); 1068da351827SJames Morse 1069da351827SJames Morse error: 1070da351827SJames Morse sdei_mark_interface_broken(); 1071da351827SJames Morse return err; 1072ad6eb31eSJames Morse } 1073ad6eb31eSJames Morse 1074ad6eb31eSJames Morse static const struct of_device_id sdei_of_match[] = { 1075ad6eb31eSJames Morse { .compatible = "arm,sdei-1.0" }, 1076ad6eb31eSJames Morse {} 1077ad6eb31eSJames Morse }; 1078ad6eb31eSJames Morse 1079ad6eb31eSJames Morse static struct platform_driver sdei_driver = { 1080ad6eb31eSJames Morse .driver = { 1081ad6eb31eSJames Morse .name = "sdei", 1082da351827SJames Morse .pm = &sdei_pm_ops, 1083ad6eb31eSJames Morse .of_match_table = sdei_of_match, 1084ad6eb31eSJames Morse }, 1085ad6eb31eSJames Morse .probe = sdei_probe, 1086ad6eb31eSJames Morse }; 1087ad6eb31eSJames Morse 1088ad6eb31eSJames Morse static bool __init sdei_present_dt(void) 1089ad6eb31eSJames Morse { 1090ad6eb31eSJames Morse struct device_node *np, *fw_np; 1091ad6eb31eSJames Morse 1092ad6eb31eSJames Morse fw_np = of_find_node_by_name(NULL, "firmware"); 1093ad6eb31eSJames Morse if (!fw_np) 1094ad6eb31eSJames Morse return false; 1095ad6eb31eSJames Morse 1096ad6eb31eSJames Morse np = of_find_matching_node(fw_np, sdei_of_match); 1097ad6eb31eSJames Morse if (!np) 1098ad6eb31eSJames Morse return false; 1099ad6eb31eSJames Morse of_node_put(np); 1100ad6eb31eSJames Morse 1101ad6eb31eSJames Morse return true; 1102ad6eb31eSJames Morse } 1103ad6eb31eSJames Morse 1104677a60bdSJames Morse static bool __init sdei_present_acpi(void) 1105677a60bdSJames Morse { 1106677a60bdSJames Morse acpi_status status; 1107677a60bdSJames Morse struct platform_device *pdev; 1108677a60bdSJames Morse struct acpi_table_header *sdei_table_header; 1109677a60bdSJames Morse 1110677a60bdSJames Morse if (acpi_disabled) 1111677a60bdSJames Morse return false; 1112677a60bdSJames Morse 1113677a60bdSJames Morse status = acpi_get_table(ACPI_SIG_SDEI, 0, &sdei_table_header); 1114677a60bdSJames Morse if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { 1115677a60bdSJames Morse const char *msg = acpi_format_exception(status); 1116677a60bdSJames Morse 1117677a60bdSJames Morse pr_info("Failed to get ACPI:SDEI table, %s\n", msg); 1118677a60bdSJames Morse } 1119677a60bdSJames Morse if (ACPI_FAILURE(status)) 1120677a60bdSJames Morse return false; 1121677a60bdSJames Morse 1122677a60bdSJames Morse pdev = platform_device_register_simple(sdei_driver.driver.name, 0, NULL, 1123677a60bdSJames Morse 0); 1124677a60bdSJames Morse if (IS_ERR(pdev)) 1125677a60bdSJames Morse return false; 1126677a60bdSJames Morse 1127677a60bdSJames Morse return true; 1128677a60bdSJames Morse } 1129677a60bdSJames Morse 1130ad6eb31eSJames Morse static int __init sdei_init(void) 1131ad6eb31eSJames Morse { 1132677a60bdSJames Morse if (sdei_present_dt() || sdei_present_acpi()) 1133ad6eb31eSJames Morse platform_driver_register(&sdei_driver); 1134ad6eb31eSJames Morse 1135ad6eb31eSJames Morse return 0; 1136ad6eb31eSJames Morse } 1137ad6eb31eSJames Morse 1138677a60bdSJames Morse /* 1139677a60bdSJames Morse * On an ACPI system SDEI needs to be ready before HEST:GHES tries to register 1140677a60bdSJames Morse * its events. ACPI is initialised from a subsys_initcall(), GHES is initialised 1141677a60bdSJames Morse * by device_initcall(). We want to be called in the middle. 1142677a60bdSJames Morse */ 1143ad6eb31eSJames Morse subsys_initcall_sync(sdei_init); 1144ad6eb31eSJames Morse 1145ad6eb31eSJames Morse int sdei_event_handler(struct pt_regs *regs, 1146ad6eb31eSJames Morse struct sdei_registered_event *arg) 1147ad6eb31eSJames Morse { 1148ad6eb31eSJames Morse int err; 1149ad6eb31eSJames Morse mm_segment_t orig_addr_limit; 1150ad6eb31eSJames Morse u32 event_num = arg->event_num; 1151ad6eb31eSJames Morse 1152ad6eb31eSJames Morse orig_addr_limit = get_fs(); 1153ad6eb31eSJames Morse set_fs(USER_DS); 1154ad6eb31eSJames Morse 1155ad6eb31eSJames Morse err = arg->callback(event_num, regs, arg->callback_arg); 1156ad6eb31eSJames Morse if (err) 1157ad6eb31eSJames Morse pr_err_ratelimited("event %u on CPU %u failed with error: %d\n", 1158ad6eb31eSJames Morse event_num, smp_processor_id(), err); 1159ad6eb31eSJames Morse 1160ad6eb31eSJames Morse set_fs(orig_addr_limit); 1161ad6eb31eSJames Morse 1162ad6eb31eSJames Morse return err; 1163ad6eb31eSJames Morse } 1164ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_event_handler); 1165