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