1*ad6eb31eSJames Morse // SPDX-License-Identifier: GPL-2.0 2*ad6eb31eSJames Morse // Copyright (C) 2017 Arm Ltd. 3*ad6eb31eSJames Morse #define pr_fmt(fmt) "sdei: " fmt 4*ad6eb31eSJames Morse 5*ad6eb31eSJames Morse #include <linux/acpi.h> 6*ad6eb31eSJames Morse #include <linux/arm_sdei.h> 7*ad6eb31eSJames Morse #include <linux/arm-smccc.h> 8*ad6eb31eSJames Morse #include <linux/bitops.h> 9*ad6eb31eSJames Morse #include <linux/compiler.h> 10*ad6eb31eSJames Morse #include <linux/errno.h> 11*ad6eb31eSJames Morse #include <linux/hardirq.h> 12*ad6eb31eSJames Morse #include <linux/kernel.h> 13*ad6eb31eSJames Morse #include <linux/kprobes.h> 14*ad6eb31eSJames Morse #include <linux/kvm_host.h> 15*ad6eb31eSJames Morse #include <linux/list.h> 16*ad6eb31eSJames Morse #include <linux/mutex.h> 17*ad6eb31eSJames Morse #include <linux/of.h> 18*ad6eb31eSJames Morse #include <linux/of_platform.h> 19*ad6eb31eSJames Morse #include <linux/percpu.h> 20*ad6eb31eSJames Morse #include <linux/platform_device.h> 21*ad6eb31eSJames Morse #include <linux/ptrace.h> 22*ad6eb31eSJames Morse #include <linux/preempt.h> 23*ad6eb31eSJames Morse #include <linux/slab.h> 24*ad6eb31eSJames Morse #include <linux/smp.h> 25*ad6eb31eSJames Morse #include <linux/spinlock.h> 26*ad6eb31eSJames Morse #include <linux/uaccess.h> 27*ad6eb31eSJames Morse 28*ad6eb31eSJames Morse /* 29*ad6eb31eSJames Morse * The call to use to reach the firmware. 30*ad6eb31eSJames Morse */ 31*ad6eb31eSJames Morse static asmlinkage void (*sdei_firmware_call)(unsigned long function_id, 32*ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 33*ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 34*ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res); 35*ad6eb31eSJames Morse 36*ad6eb31eSJames Morse /* entry point from firmware to arch asm code */ 37*ad6eb31eSJames Morse static unsigned long sdei_entry_point; 38*ad6eb31eSJames Morse 39*ad6eb31eSJames Morse struct sdei_event { 40*ad6eb31eSJames Morse struct list_head list; 41*ad6eb31eSJames Morse u32 event_num; 42*ad6eb31eSJames Morse u8 type; 43*ad6eb31eSJames Morse u8 priority; 44*ad6eb31eSJames Morse 45*ad6eb31eSJames Morse /* This pointer is handed to firmware as the event argument. */ 46*ad6eb31eSJames Morse struct sdei_registered_event *registered; 47*ad6eb31eSJames Morse }; 48*ad6eb31eSJames Morse 49*ad6eb31eSJames Morse /* Take the mutex for any API call or modification. Take the mutex first. */ 50*ad6eb31eSJames Morse static DEFINE_MUTEX(sdei_events_lock); 51*ad6eb31eSJames Morse 52*ad6eb31eSJames Morse /* and then hold this when modifying the list */ 53*ad6eb31eSJames Morse static DEFINE_SPINLOCK(sdei_list_lock); 54*ad6eb31eSJames Morse static LIST_HEAD(sdei_list); 55*ad6eb31eSJames Morse 56*ad6eb31eSJames Morse static int sdei_to_linux_errno(unsigned long sdei_err) 57*ad6eb31eSJames Morse { 58*ad6eb31eSJames Morse switch (sdei_err) { 59*ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 60*ad6eb31eSJames Morse return -EOPNOTSUPP; 61*ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 62*ad6eb31eSJames Morse return -EINVAL; 63*ad6eb31eSJames Morse case SDEI_DENIED: 64*ad6eb31eSJames Morse return -EPERM; 65*ad6eb31eSJames Morse case SDEI_PENDING: 66*ad6eb31eSJames Morse return -EINPROGRESS; 67*ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 68*ad6eb31eSJames Morse return -ENOMEM; 69*ad6eb31eSJames Morse } 70*ad6eb31eSJames Morse 71*ad6eb31eSJames Morse /* Not an error value ... */ 72*ad6eb31eSJames Morse return sdei_err; 73*ad6eb31eSJames Morse } 74*ad6eb31eSJames Morse 75*ad6eb31eSJames Morse /* 76*ad6eb31eSJames Morse * If x0 is any of these values, then the call failed, use sdei_to_linux_errno() 77*ad6eb31eSJames Morse * to translate. 78*ad6eb31eSJames Morse */ 79*ad6eb31eSJames Morse static int sdei_is_err(struct arm_smccc_res *res) 80*ad6eb31eSJames Morse { 81*ad6eb31eSJames Morse switch (res->a0) { 82*ad6eb31eSJames Morse case SDEI_NOT_SUPPORTED: 83*ad6eb31eSJames Morse case SDEI_INVALID_PARAMETERS: 84*ad6eb31eSJames Morse case SDEI_DENIED: 85*ad6eb31eSJames Morse case SDEI_PENDING: 86*ad6eb31eSJames Morse case SDEI_OUT_OF_RESOURCE: 87*ad6eb31eSJames Morse return true; 88*ad6eb31eSJames Morse } 89*ad6eb31eSJames Morse 90*ad6eb31eSJames Morse return false; 91*ad6eb31eSJames Morse } 92*ad6eb31eSJames Morse 93*ad6eb31eSJames Morse static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0, 94*ad6eb31eSJames Morse unsigned long arg1, unsigned long arg2, 95*ad6eb31eSJames Morse unsigned long arg3, unsigned long arg4, 96*ad6eb31eSJames Morse u64 *result) 97*ad6eb31eSJames Morse { 98*ad6eb31eSJames Morse int err = 0; 99*ad6eb31eSJames Morse struct arm_smccc_res res; 100*ad6eb31eSJames Morse 101*ad6eb31eSJames Morse if (sdei_firmware_call) { 102*ad6eb31eSJames Morse sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4, 103*ad6eb31eSJames Morse &res); 104*ad6eb31eSJames Morse if (sdei_is_err(&res)) 105*ad6eb31eSJames Morse err = sdei_to_linux_errno(res.a0); 106*ad6eb31eSJames Morse } else { 107*ad6eb31eSJames Morse /* 108*ad6eb31eSJames Morse * !sdei_firmware_call means we failed to probe or called 109*ad6eb31eSJames Morse * sdei_mark_interface_broken(). -EIO is not an error returned 110*ad6eb31eSJames Morse * by sdei_to_linux_errno() and is used to suppress messages 111*ad6eb31eSJames Morse * from this driver. 112*ad6eb31eSJames Morse */ 113*ad6eb31eSJames Morse err = -EIO; 114*ad6eb31eSJames Morse res.a0 = SDEI_NOT_SUPPORTED; 115*ad6eb31eSJames Morse } 116*ad6eb31eSJames Morse 117*ad6eb31eSJames Morse if (result) 118*ad6eb31eSJames Morse *result = res.a0; 119*ad6eb31eSJames Morse 120*ad6eb31eSJames Morse return err; 121*ad6eb31eSJames Morse } 122*ad6eb31eSJames Morse 123*ad6eb31eSJames Morse static struct sdei_event *sdei_event_find(u32 event_num) 124*ad6eb31eSJames Morse { 125*ad6eb31eSJames Morse struct sdei_event *e, *found = NULL; 126*ad6eb31eSJames Morse 127*ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 128*ad6eb31eSJames Morse 129*ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 130*ad6eb31eSJames Morse list_for_each_entry(e, &sdei_list, list) { 131*ad6eb31eSJames Morse if (e->event_num == event_num) { 132*ad6eb31eSJames Morse found = e; 133*ad6eb31eSJames Morse break; 134*ad6eb31eSJames Morse } 135*ad6eb31eSJames Morse } 136*ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 137*ad6eb31eSJames Morse 138*ad6eb31eSJames Morse return found; 139*ad6eb31eSJames Morse } 140*ad6eb31eSJames Morse 141*ad6eb31eSJames Morse int sdei_api_event_context(u32 query, u64 *result) 142*ad6eb31eSJames Morse { 143*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0, 144*ad6eb31eSJames Morse result); 145*ad6eb31eSJames Morse } 146*ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_api_event_context); 147*ad6eb31eSJames Morse 148*ad6eb31eSJames Morse static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) 149*ad6eb31eSJames Morse { 150*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, 151*ad6eb31eSJames Morse 0, 0, result); 152*ad6eb31eSJames Morse } 153*ad6eb31eSJames Morse 154*ad6eb31eSJames Morse static struct sdei_event *sdei_event_create(u32 event_num, 155*ad6eb31eSJames Morse sdei_event_callback *cb, 156*ad6eb31eSJames Morse void *cb_arg) 157*ad6eb31eSJames Morse { 158*ad6eb31eSJames Morse int err; 159*ad6eb31eSJames Morse u64 result; 160*ad6eb31eSJames Morse struct sdei_event *event; 161*ad6eb31eSJames Morse struct sdei_registered_event *reg; 162*ad6eb31eSJames Morse 163*ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 164*ad6eb31eSJames Morse 165*ad6eb31eSJames Morse event = kzalloc(sizeof(*event), GFP_KERNEL); 166*ad6eb31eSJames Morse if (!event) 167*ad6eb31eSJames Morse return ERR_PTR(-ENOMEM); 168*ad6eb31eSJames Morse 169*ad6eb31eSJames Morse INIT_LIST_HEAD(&event->list); 170*ad6eb31eSJames Morse event->event_num = event_num; 171*ad6eb31eSJames Morse 172*ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY, 173*ad6eb31eSJames Morse &result); 174*ad6eb31eSJames Morse if (err) { 175*ad6eb31eSJames Morse kfree(event); 176*ad6eb31eSJames Morse return ERR_PTR(err); 177*ad6eb31eSJames Morse } 178*ad6eb31eSJames Morse event->priority = result; 179*ad6eb31eSJames Morse 180*ad6eb31eSJames Morse err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE, 181*ad6eb31eSJames Morse &result); 182*ad6eb31eSJames Morse if (err) { 183*ad6eb31eSJames Morse kfree(event); 184*ad6eb31eSJames Morse return ERR_PTR(err); 185*ad6eb31eSJames Morse } 186*ad6eb31eSJames Morse event->type = result; 187*ad6eb31eSJames Morse 188*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) { 189*ad6eb31eSJames Morse reg = kzalloc(sizeof(*reg), GFP_KERNEL); 190*ad6eb31eSJames Morse if (!reg) { 191*ad6eb31eSJames Morse kfree(event); 192*ad6eb31eSJames Morse return ERR_PTR(-ENOMEM); 193*ad6eb31eSJames Morse } 194*ad6eb31eSJames Morse 195*ad6eb31eSJames Morse reg->event_num = event_num; 196*ad6eb31eSJames Morse reg->priority = event->priority; 197*ad6eb31eSJames Morse 198*ad6eb31eSJames Morse reg->callback = cb; 199*ad6eb31eSJames Morse reg->callback_arg = cb_arg; 200*ad6eb31eSJames Morse event->registered = reg; 201*ad6eb31eSJames Morse } 202*ad6eb31eSJames Morse 203*ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 204*ad6eb31eSJames Morse kfree(event->registered); 205*ad6eb31eSJames Morse kfree(event); 206*ad6eb31eSJames Morse event = ERR_PTR(-EBUSY); 207*ad6eb31eSJames Morse } else { 208*ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 209*ad6eb31eSJames Morse list_add(&event->list, &sdei_list); 210*ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 211*ad6eb31eSJames Morse } 212*ad6eb31eSJames Morse 213*ad6eb31eSJames Morse return event; 214*ad6eb31eSJames Morse } 215*ad6eb31eSJames Morse 216*ad6eb31eSJames Morse static void sdei_event_destroy(struct sdei_event *event) 217*ad6eb31eSJames Morse { 218*ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 219*ad6eb31eSJames Morse 220*ad6eb31eSJames Morse spin_lock(&sdei_list_lock); 221*ad6eb31eSJames Morse list_del(&event->list); 222*ad6eb31eSJames Morse spin_unlock(&sdei_list_lock); 223*ad6eb31eSJames Morse 224*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 225*ad6eb31eSJames Morse kfree(event->registered); 226*ad6eb31eSJames Morse 227*ad6eb31eSJames Morse kfree(event); 228*ad6eb31eSJames Morse } 229*ad6eb31eSJames Morse 230*ad6eb31eSJames Morse static int sdei_api_get_version(u64 *version) 231*ad6eb31eSJames Morse { 232*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version); 233*ad6eb31eSJames Morse } 234*ad6eb31eSJames Morse 235*ad6eb31eSJames Morse int sdei_mask_local_cpu(void) 236*ad6eb31eSJames Morse { 237*ad6eb31eSJames Morse int err; 238*ad6eb31eSJames Morse 239*ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 240*ad6eb31eSJames Morse 241*ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL); 242*ad6eb31eSJames Morse if (err && err != -EIO) { 243*ad6eb31eSJames Morse pr_warn_once("failed to mask CPU[%u]: %d\n", 244*ad6eb31eSJames Morse smp_processor_id(), err); 245*ad6eb31eSJames Morse return err; 246*ad6eb31eSJames Morse } 247*ad6eb31eSJames Morse 248*ad6eb31eSJames Morse return 0; 249*ad6eb31eSJames Morse } 250*ad6eb31eSJames Morse 251*ad6eb31eSJames Morse static void _ipi_mask_cpu(void *ignored) 252*ad6eb31eSJames Morse { 253*ad6eb31eSJames Morse sdei_mask_local_cpu(); 254*ad6eb31eSJames Morse } 255*ad6eb31eSJames Morse 256*ad6eb31eSJames Morse int sdei_unmask_local_cpu(void) 257*ad6eb31eSJames Morse { 258*ad6eb31eSJames Morse int err; 259*ad6eb31eSJames Morse 260*ad6eb31eSJames Morse WARN_ON_ONCE(preemptible()); 261*ad6eb31eSJames Morse 262*ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL); 263*ad6eb31eSJames Morse if (err && err != -EIO) { 264*ad6eb31eSJames Morse pr_warn_once("failed to unmask CPU[%u]: %d\n", 265*ad6eb31eSJames Morse smp_processor_id(), err); 266*ad6eb31eSJames Morse return err; 267*ad6eb31eSJames Morse } 268*ad6eb31eSJames Morse 269*ad6eb31eSJames Morse return 0; 270*ad6eb31eSJames Morse } 271*ad6eb31eSJames Morse 272*ad6eb31eSJames Morse static void _ipi_unmask_cpu(void *ignored) 273*ad6eb31eSJames Morse { 274*ad6eb31eSJames Morse sdei_unmask_local_cpu(); 275*ad6eb31eSJames Morse } 276*ad6eb31eSJames Morse 277*ad6eb31eSJames Morse static void _ipi_private_reset(void *ignored) 278*ad6eb31eSJames Morse { 279*ad6eb31eSJames Morse int err; 280*ad6eb31eSJames Morse 281*ad6eb31eSJames Morse err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0, 282*ad6eb31eSJames Morse NULL); 283*ad6eb31eSJames Morse if (err && err != -EIO) 284*ad6eb31eSJames Morse pr_warn_once("failed to reset CPU[%u]: %d\n", 285*ad6eb31eSJames Morse smp_processor_id(), err); 286*ad6eb31eSJames Morse } 287*ad6eb31eSJames Morse 288*ad6eb31eSJames Morse static int sdei_api_shared_reset(void) 289*ad6eb31eSJames Morse { 290*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0, 291*ad6eb31eSJames Morse NULL); 292*ad6eb31eSJames Morse } 293*ad6eb31eSJames Morse 294*ad6eb31eSJames Morse static void sdei_mark_interface_broken(void) 295*ad6eb31eSJames Morse { 296*ad6eb31eSJames Morse pr_err("disabling SDEI firmware interface\n"); 297*ad6eb31eSJames Morse on_each_cpu(&_ipi_mask_cpu, NULL, true); 298*ad6eb31eSJames Morse sdei_firmware_call = NULL; 299*ad6eb31eSJames Morse } 300*ad6eb31eSJames Morse 301*ad6eb31eSJames Morse static int sdei_platform_reset(void) 302*ad6eb31eSJames Morse { 303*ad6eb31eSJames Morse int err; 304*ad6eb31eSJames Morse 305*ad6eb31eSJames Morse on_each_cpu(&_ipi_private_reset, NULL, true); 306*ad6eb31eSJames Morse err = sdei_api_shared_reset(); 307*ad6eb31eSJames Morse if (err) { 308*ad6eb31eSJames Morse pr_err("Failed to reset platform: %d\n", err); 309*ad6eb31eSJames Morse sdei_mark_interface_broken(); 310*ad6eb31eSJames Morse } 311*ad6eb31eSJames Morse 312*ad6eb31eSJames Morse return err; 313*ad6eb31eSJames Morse } 314*ad6eb31eSJames Morse 315*ad6eb31eSJames Morse static int sdei_api_event_enable(u32 event_num) 316*ad6eb31eSJames Morse { 317*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 318*ad6eb31eSJames Morse 0, NULL); 319*ad6eb31eSJames Morse } 320*ad6eb31eSJames Morse 321*ad6eb31eSJames Morse int sdei_event_enable(u32 event_num) 322*ad6eb31eSJames Morse { 323*ad6eb31eSJames Morse int err = -EINVAL; 324*ad6eb31eSJames Morse struct sdei_event *event; 325*ad6eb31eSJames Morse 326*ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 327*ad6eb31eSJames Morse event = sdei_event_find(event_num); 328*ad6eb31eSJames Morse if (!event) { 329*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 330*ad6eb31eSJames Morse return -ENOENT; 331*ad6eb31eSJames Morse } 332*ad6eb31eSJames Morse 333*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 334*ad6eb31eSJames Morse err = sdei_api_event_enable(event->event_num); 335*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 336*ad6eb31eSJames Morse 337*ad6eb31eSJames Morse return err; 338*ad6eb31eSJames Morse } 339*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_enable); 340*ad6eb31eSJames Morse 341*ad6eb31eSJames Morse static int sdei_api_event_disable(u32 event_num) 342*ad6eb31eSJames Morse { 343*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 344*ad6eb31eSJames Morse 0, 0, NULL); 345*ad6eb31eSJames Morse } 346*ad6eb31eSJames Morse 347*ad6eb31eSJames Morse int sdei_event_disable(u32 event_num) 348*ad6eb31eSJames Morse { 349*ad6eb31eSJames Morse int err = -EINVAL; 350*ad6eb31eSJames Morse struct sdei_event *event; 351*ad6eb31eSJames Morse 352*ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 353*ad6eb31eSJames Morse event = sdei_event_find(event_num); 354*ad6eb31eSJames Morse if (!event) { 355*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 356*ad6eb31eSJames Morse return -ENOENT; 357*ad6eb31eSJames Morse } 358*ad6eb31eSJames Morse 359*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 360*ad6eb31eSJames Morse err = sdei_api_event_disable(event->event_num); 361*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 362*ad6eb31eSJames Morse 363*ad6eb31eSJames Morse return err; 364*ad6eb31eSJames Morse } 365*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_disable); 366*ad6eb31eSJames Morse 367*ad6eb31eSJames Morse static int sdei_api_event_unregister(u32 event_num) 368*ad6eb31eSJames Morse { 369*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0, 370*ad6eb31eSJames Morse 0, 0, 0, NULL); 371*ad6eb31eSJames Morse } 372*ad6eb31eSJames Morse 373*ad6eb31eSJames Morse static int _sdei_event_unregister(struct sdei_event *event) 374*ad6eb31eSJames Morse { 375*ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 376*ad6eb31eSJames Morse 377*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 378*ad6eb31eSJames Morse return sdei_api_event_unregister(event->event_num); 379*ad6eb31eSJames Morse 380*ad6eb31eSJames Morse return -EINVAL; 381*ad6eb31eSJames Morse } 382*ad6eb31eSJames Morse 383*ad6eb31eSJames Morse int sdei_event_unregister(u32 event_num) 384*ad6eb31eSJames Morse { 385*ad6eb31eSJames Morse int err; 386*ad6eb31eSJames Morse struct sdei_event *event; 387*ad6eb31eSJames Morse 388*ad6eb31eSJames Morse WARN_ON(in_nmi()); 389*ad6eb31eSJames Morse 390*ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 391*ad6eb31eSJames Morse event = sdei_event_find(event_num); 392*ad6eb31eSJames Morse do { 393*ad6eb31eSJames Morse if (!event) { 394*ad6eb31eSJames Morse pr_warn("Event %u not registered\n", event_num); 395*ad6eb31eSJames Morse err = -ENOENT; 396*ad6eb31eSJames Morse break; 397*ad6eb31eSJames Morse } 398*ad6eb31eSJames Morse 399*ad6eb31eSJames Morse err = _sdei_event_unregister(event); 400*ad6eb31eSJames Morse if (err) 401*ad6eb31eSJames Morse break; 402*ad6eb31eSJames Morse 403*ad6eb31eSJames Morse sdei_event_destroy(event); 404*ad6eb31eSJames Morse } while (0); 405*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 406*ad6eb31eSJames Morse 407*ad6eb31eSJames Morse return err; 408*ad6eb31eSJames Morse } 409*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_unregister); 410*ad6eb31eSJames Morse 411*ad6eb31eSJames Morse static int sdei_api_event_register(u32 event_num, unsigned long entry_point, 412*ad6eb31eSJames Morse void *arg, u64 flags, u64 affinity) 413*ad6eb31eSJames Morse { 414*ad6eb31eSJames Morse return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num, 415*ad6eb31eSJames Morse (unsigned long)entry_point, (unsigned long)arg, 416*ad6eb31eSJames Morse flags, affinity, NULL); 417*ad6eb31eSJames Morse } 418*ad6eb31eSJames Morse 419*ad6eb31eSJames Morse static int _sdei_event_register(struct sdei_event *event) 420*ad6eb31eSJames Morse { 421*ad6eb31eSJames Morse lockdep_assert_held(&sdei_events_lock); 422*ad6eb31eSJames Morse 423*ad6eb31eSJames Morse if (event->type == SDEI_EVENT_TYPE_SHARED) 424*ad6eb31eSJames Morse return sdei_api_event_register(event->event_num, 425*ad6eb31eSJames Morse sdei_entry_point, 426*ad6eb31eSJames Morse event->registered, 427*ad6eb31eSJames Morse SDEI_EVENT_REGISTER_RM_ANY, 0); 428*ad6eb31eSJames Morse 429*ad6eb31eSJames Morse return -EINVAL; 430*ad6eb31eSJames Morse } 431*ad6eb31eSJames Morse 432*ad6eb31eSJames Morse int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg) 433*ad6eb31eSJames Morse { 434*ad6eb31eSJames Morse int err; 435*ad6eb31eSJames Morse struct sdei_event *event; 436*ad6eb31eSJames Morse 437*ad6eb31eSJames Morse WARN_ON(in_nmi()); 438*ad6eb31eSJames Morse 439*ad6eb31eSJames Morse mutex_lock(&sdei_events_lock); 440*ad6eb31eSJames Morse do { 441*ad6eb31eSJames Morse if (sdei_event_find(event_num)) { 442*ad6eb31eSJames Morse pr_warn("Event %u already registered\n", event_num); 443*ad6eb31eSJames Morse err = -EBUSY; 444*ad6eb31eSJames Morse break; 445*ad6eb31eSJames Morse } 446*ad6eb31eSJames Morse 447*ad6eb31eSJames Morse event = sdei_event_create(event_num, cb, arg); 448*ad6eb31eSJames Morse if (IS_ERR(event)) { 449*ad6eb31eSJames Morse err = PTR_ERR(event); 450*ad6eb31eSJames Morse pr_warn("Failed to create event %u: %d\n", event_num, 451*ad6eb31eSJames Morse err); 452*ad6eb31eSJames Morse break; 453*ad6eb31eSJames Morse } 454*ad6eb31eSJames Morse 455*ad6eb31eSJames Morse err = _sdei_event_register(event); 456*ad6eb31eSJames Morse if (err) { 457*ad6eb31eSJames Morse sdei_event_destroy(event); 458*ad6eb31eSJames Morse pr_warn("Failed to register event %u: %d\n", event_num, 459*ad6eb31eSJames Morse err); 460*ad6eb31eSJames Morse } 461*ad6eb31eSJames Morse } while (0); 462*ad6eb31eSJames Morse mutex_unlock(&sdei_events_lock); 463*ad6eb31eSJames Morse 464*ad6eb31eSJames Morse return err; 465*ad6eb31eSJames Morse } 466*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_register); 467*ad6eb31eSJames Morse 468*ad6eb31eSJames Morse static void sdei_smccc_smc(unsigned long function_id, 469*ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 470*ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 471*ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 472*ad6eb31eSJames Morse { 473*ad6eb31eSJames Morse arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 474*ad6eb31eSJames Morse } 475*ad6eb31eSJames Morse 476*ad6eb31eSJames Morse static void sdei_smccc_hvc(unsigned long function_id, 477*ad6eb31eSJames Morse unsigned long arg0, unsigned long arg1, 478*ad6eb31eSJames Morse unsigned long arg2, unsigned long arg3, 479*ad6eb31eSJames Morse unsigned long arg4, struct arm_smccc_res *res) 480*ad6eb31eSJames Morse { 481*ad6eb31eSJames Morse arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res); 482*ad6eb31eSJames Morse } 483*ad6eb31eSJames Morse 484*ad6eb31eSJames Morse static int sdei_get_conduit(struct platform_device *pdev) 485*ad6eb31eSJames Morse { 486*ad6eb31eSJames Morse const char *method; 487*ad6eb31eSJames Morse struct device_node *np = pdev->dev.of_node; 488*ad6eb31eSJames Morse 489*ad6eb31eSJames Morse sdei_firmware_call = NULL; 490*ad6eb31eSJames Morse if (np) { 491*ad6eb31eSJames Morse if (of_property_read_string(np, "method", &method)) { 492*ad6eb31eSJames Morse pr_warn("missing \"method\" property\n"); 493*ad6eb31eSJames Morse return CONDUIT_INVALID; 494*ad6eb31eSJames Morse } 495*ad6eb31eSJames Morse 496*ad6eb31eSJames Morse if (!strcmp("hvc", method)) { 497*ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_hvc; 498*ad6eb31eSJames Morse return CONDUIT_HVC; 499*ad6eb31eSJames Morse } else if (!strcmp("smc", method)) { 500*ad6eb31eSJames Morse sdei_firmware_call = &sdei_smccc_smc; 501*ad6eb31eSJames Morse return CONDUIT_SMC; 502*ad6eb31eSJames Morse } 503*ad6eb31eSJames Morse 504*ad6eb31eSJames Morse pr_warn("invalid \"method\" property: %s\n", method); 505*ad6eb31eSJames Morse } 506*ad6eb31eSJames Morse 507*ad6eb31eSJames Morse return CONDUIT_INVALID; 508*ad6eb31eSJames Morse } 509*ad6eb31eSJames Morse 510*ad6eb31eSJames Morse static int sdei_probe(struct platform_device *pdev) 511*ad6eb31eSJames Morse { 512*ad6eb31eSJames Morse int err; 513*ad6eb31eSJames Morse u64 ver = 0; 514*ad6eb31eSJames Morse int conduit; 515*ad6eb31eSJames Morse 516*ad6eb31eSJames Morse conduit = sdei_get_conduit(pdev); 517*ad6eb31eSJames Morse if (!sdei_firmware_call) 518*ad6eb31eSJames Morse return 0; 519*ad6eb31eSJames Morse 520*ad6eb31eSJames Morse err = sdei_api_get_version(&ver); 521*ad6eb31eSJames Morse if (err == -EOPNOTSUPP) 522*ad6eb31eSJames Morse pr_err("advertised but not implemented in platform firmware\n"); 523*ad6eb31eSJames Morse if (err) { 524*ad6eb31eSJames Morse pr_err("Failed to get SDEI version: %d\n", err); 525*ad6eb31eSJames Morse sdei_mark_interface_broken(); 526*ad6eb31eSJames Morse return err; 527*ad6eb31eSJames Morse } 528*ad6eb31eSJames Morse 529*ad6eb31eSJames Morse pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n", 530*ad6eb31eSJames Morse (int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver), 531*ad6eb31eSJames Morse (int)SDEI_VERSION_VENDOR(ver)); 532*ad6eb31eSJames Morse 533*ad6eb31eSJames Morse if (SDEI_VERSION_MAJOR(ver) != 1) { 534*ad6eb31eSJames Morse pr_warn("Conflicting SDEI version detected.\n"); 535*ad6eb31eSJames Morse sdei_mark_interface_broken(); 536*ad6eb31eSJames Morse return -EINVAL; 537*ad6eb31eSJames Morse } 538*ad6eb31eSJames Morse 539*ad6eb31eSJames Morse err = sdei_platform_reset(); 540*ad6eb31eSJames Morse if (err) 541*ad6eb31eSJames Morse return err; 542*ad6eb31eSJames Morse 543*ad6eb31eSJames Morse sdei_entry_point = sdei_arch_get_entry_point(conduit); 544*ad6eb31eSJames Morse if (!sdei_entry_point) { 545*ad6eb31eSJames Morse /* Not supported due to hardware or boot configuration */ 546*ad6eb31eSJames Morse sdei_mark_interface_broken(); 547*ad6eb31eSJames Morse return 0; 548*ad6eb31eSJames Morse } 549*ad6eb31eSJames Morse 550*ad6eb31eSJames Morse on_each_cpu(&_ipi_unmask_cpu, NULL, false); 551*ad6eb31eSJames Morse 552*ad6eb31eSJames Morse return 0; 553*ad6eb31eSJames Morse } 554*ad6eb31eSJames Morse 555*ad6eb31eSJames Morse static const struct of_device_id sdei_of_match[] = { 556*ad6eb31eSJames Morse { .compatible = "arm,sdei-1.0" }, 557*ad6eb31eSJames Morse {} 558*ad6eb31eSJames Morse }; 559*ad6eb31eSJames Morse 560*ad6eb31eSJames Morse static struct platform_driver sdei_driver = { 561*ad6eb31eSJames Morse .driver = { 562*ad6eb31eSJames Morse .name = "sdei", 563*ad6eb31eSJames Morse .of_match_table = sdei_of_match, 564*ad6eb31eSJames Morse }, 565*ad6eb31eSJames Morse .probe = sdei_probe, 566*ad6eb31eSJames Morse }; 567*ad6eb31eSJames Morse 568*ad6eb31eSJames Morse static bool __init sdei_present_dt(void) 569*ad6eb31eSJames Morse { 570*ad6eb31eSJames Morse struct platform_device *pdev; 571*ad6eb31eSJames Morse struct device_node *np, *fw_np; 572*ad6eb31eSJames Morse 573*ad6eb31eSJames Morse fw_np = of_find_node_by_name(NULL, "firmware"); 574*ad6eb31eSJames Morse if (!fw_np) 575*ad6eb31eSJames Morse return false; 576*ad6eb31eSJames Morse 577*ad6eb31eSJames Morse np = of_find_matching_node(fw_np, sdei_of_match); 578*ad6eb31eSJames Morse of_node_put(fw_np); 579*ad6eb31eSJames Morse if (!np) 580*ad6eb31eSJames Morse return false; 581*ad6eb31eSJames Morse 582*ad6eb31eSJames Morse pdev = of_platform_device_create(np, sdei_driver.driver.name, NULL); 583*ad6eb31eSJames Morse of_node_put(np); 584*ad6eb31eSJames Morse if (IS_ERR(pdev)) 585*ad6eb31eSJames Morse return false; 586*ad6eb31eSJames Morse 587*ad6eb31eSJames Morse return true; 588*ad6eb31eSJames Morse } 589*ad6eb31eSJames Morse 590*ad6eb31eSJames Morse static int __init sdei_init(void) 591*ad6eb31eSJames Morse { 592*ad6eb31eSJames Morse if (sdei_present_dt()) 593*ad6eb31eSJames Morse platform_driver_register(&sdei_driver); 594*ad6eb31eSJames Morse 595*ad6eb31eSJames Morse return 0; 596*ad6eb31eSJames Morse } 597*ad6eb31eSJames Morse 598*ad6eb31eSJames Morse subsys_initcall_sync(sdei_init); 599*ad6eb31eSJames Morse 600*ad6eb31eSJames Morse int sdei_event_handler(struct pt_regs *regs, 601*ad6eb31eSJames Morse struct sdei_registered_event *arg) 602*ad6eb31eSJames Morse { 603*ad6eb31eSJames Morse int err; 604*ad6eb31eSJames Morse mm_segment_t orig_addr_limit; 605*ad6eb31eSJames Morse u32 event_num = arg->event_num; 606*ad6eb31eSJames Morse 607*ad6eb31eSJames Morse orig_addr_limit = get_fs(); 608*ad6eb31eSJames Morse set_fs(USER_DS); 609*ad6eb31eSJames Morse 610*ad6eb31eSJames Morse err = arg->callback(event_num, regs, arg->callback_arg); 611*ad6eb31eSJames Morse if (err) 612*ad6eb31eSJames Morse pr_err_ratelimited("event %u on CPU %u failed with error: %d\n", 613*ad6eb31eSJames Morse event_num, smp_processor_id(), err); 614*ad6eb31eSJames Morse 615*ad6eb31eSJames Morse set_fs(orig_addr_limit); 616*ad6eb31eSJames Morse 617*ad6eb31eSJames Morse return err; 618*ad6eb31eSJames Morse } 619*ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_event_handler); 620