16861d27cSMihai Carabas // SPDX-License-Identifier: GPL-2.0+ 26861d27cSMihai Carabas /* 36861d27cSMihai Carabas * Pvpanic Device Support 46861d27cSMihai Carabas * 56861d27cSMihai Carabas * Copyright (C) 2013 Fujitsu. 66861d27cSMihai Carabas * Copyright (C) 2018 ZTE. 76861d27cSMihai Carabas * Copyright (C) 2021 Oracle. 86861d27cSMihai Carabas */ 96861d27cSMihai Carabas 10c1426d39SThomas Weißschuh #include <linux/device.h> 117037f714SAndy Shevchenko #include <linux/errno.h> 127037f714SAndy Shevchenko #include <linux/gfp_types.h> 136861d27cSMihai Carabas #include <linux/io.h> 146861d27cSMihai Carabas #include <linux/kexec.h> 157037f714SAndy Shevchenko #include <linux/kstrtox.h> 167037f714SAndy Shevchenko #include <linux/limits.h> 177037f714SAndy Shevchenko #include <linux/list.h> 186861d27cSMihai Carabas #include <linux/mod_devicetable.h> 196861d27cSMihai Carabas #include <linux/module.h> 20f39650deSAndy Shevchenko #include <linux/panic_notifier.h> 217037f714SAndy Shevchenko #include <linux/platform_device.h> 22*7b51b137SThomas Weißschuh #include <linux/reboot.h> 237037f714SAndy Shevchenko #include <linux/spinlock.h> 247037f714SAndy Shevchenko #include <linux/sysfs.h> 256861d27cSMihai Carabas #include <linux/types.h> 266861d27cSMihai Carabas 276861d27cSMihai Carabas #include <uapi/misc/pvpanic.h> 286861d27cSMihai Carabas 296861d27cSMihai Carabas #include "pvpanic.h" 306861d27cSMihai Carabas 316861d27cSMihai Carabas MODULE_AUTHOR("Mihai Carabas <mihai.carabas@oracle.com>"); 326861d27cSMihai Carabas MODULE_DESCRIPTION("pvpanic device driver"); 336861d27cSMihai Carabas MODULE_LICENSE("GPL"); 346861d27cSMihai Carabas 35c1426d39SThomas Weißschuh struct pvpanic_instance { 36c1426d39SThomas Weißschuh void __iomem *base; 37c1426d39SThomas Weißschuh unsigned int capability; 38c1426d39SThomas Weißschuh unsigned int events; 39*7b51b137SThomas Weißschuh struct sys_off_handler *sys_off; 40c1426d39SThomas Weißschuh struct list_head list; 41c1426d39SThomas Weißschuh }; 42c1426d39SThomas Weißschuh 43391e2415SYueHaibing static struct list_head pvpanic_list; 44391e2415SYueHaibing static spinlock_t pvpanic_lock; 456861d27cSMihai Carabas 466861d27cSMihai Carabas static void 476861d27cSMihai Carabas pvpanic_send_event(unsigned int event) 486861d27cSMihai Carabas { 49b3c0f877SMihai Carabas struct pvpanic_instance *pi_cur; 50b3c0f877SMihai Carabas 51e918c102SGuilherme G. Piccoli if (!spin_trylock(&pvpanic_lock)) 52e918c102SGuilherme G. Piccoli return; 53e918c102SGuilherme G. Piccoli 54b3c0f877SMihai Carabas list_for_each_entry(pi_cur, &pvpanic_list, list) { 55b3c0f877SMihai Carabas if (event & pi_cur->capability & pi_cur->events) 56b3c0f877SMihai Carabas iowrite8(event, pi_cur->base); 57b3c0f877SMihai Carabas } 58b3c0f877SMihai Carabas spin_unlock(&pvpanic_lock); 596861d27cSMihai Carabas } 606861d27cSMihai Carabas 616861d27cSMihai Carabas static int 6284b0f12aSAndy Shevchenko pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, void *unused) 636861d27cSMihai Carabas { 646861d27cSMihai Carabas unsigned int event = PVPANIC_PANICKED; 656861d27cSMihai Carabas 666861d27cSMihai Carabas if (kexec_crash_loaded()) 676861d27cSMihai Carabas event = PVPANIC_CRASH_LOADED; 686861d27cSMihai Carabas 696861d27cSMihai Carabas pvpanic_send_event(event); 706861d27cSMihai Carabas 716861d27cSMihai Carabas return NOTIFY_DONE; 726861d27cSMihai Carabas } 736861d27cSMihai Carabas 74e918c102SGuilherme G. Piccoli /* 75e918c102SGuilherme G. Piccoli * Call our notifier very early on panic, deferring the 76e918c102SGuilherme G. Piccoli * action taken to the hypervisor. 77e918c102SGuilherme G. Piccoli */ 786861d27cSMihai Carabas static struct notifier_block pvpanic_panic_nb = { 796861d27cSMihai Carabas .notifier_call = pvpanic_panic_notify, 80e918c102SGuilherme G. Piccoli .priority = INT_MAX, 816861d27cSMihai Carabas }; 826861d27cSMihai Carabas 83*7b51b137SThomas Weißschuh static int pvpanic_sys_off(struct sys_off_data *data) 84*7b51b137SThomas Weißschuh { 85*7b51b137SThomas Weißschuh pvpanic_send_event(PVPANIC_SHUTDOWN); 86*7b51b137SThomas Weißschuh 87*7b51b137SThomas Weißschuh return NOTIFY_DONE; 88*7b51b137SThomas Weißschuh } 89*7b51b137SThomas Weißschuh 90*7b51b137SThomas Weißschuh static void pvpanic_synchronize_sys_off_handler(struct device *dev, struct pvpanic_instance *pi) 91*7b51b137SThomas Weißschuh { 92*7b51b137SThomas Weißschuh /* The kernel core has logic to fall back to system halt if no 93*7b51b137SThomas Weißschuh * sys_off_handler is registered. 94*7b51b137SThomas Weißschuh * When the pvpanic sys_off_handler is disabled via sysfs the kernel 95*7b51b137SThomas Weißschuh * should use that fallback logic, so the handler needs to be unregistered. 96*7b51b137SThomas Weißschuh */ 97*7b51b137SThomas Weißschuh 98*7b51b137SThomas Weißschuh struct sys_off_handler *sys_off; 99*7b51b137SThomas Weißschuh 100*7b51b137SThomas Weißschuh if (!(pi->events & PVPANIC_SHUTDOWN) == !pi->sys_off) 101*7b51b137SThomas Weißschuh return; 102*7b51b137SThomas Weißschuh 103*7b51b137SThomas Weißschuh if (!pi->sys_off) { 104*7b51b137SThomas Weißschuh sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_LOW, 105*7b51b137SThomas Weißschuh pvpanic_sys_off, NULL); 106*7b51b137SThomas Weißschuh if (IS_ERR(sys_off)) 107*7b51b137SThomas Weißschuh dev_warn(dev, "Could not register sys_off_handler: %pe\n", sys_off); 108*7b51b137SThomas Weißschuh else 109*7b51b137SThomas Weißschuh pi->sys_off = sys_off; 110*7b51b137SThomas Weißschuh } else { 111*7b51b137SThomas Weißschuh unregister_sys_off_handler(pi->sys_off); 112*7b51b137SThomas Weißschuh pi->sys_off = NULL; 113*7b51b137SThomas Weißschuh } 114*7b51b137SThomas Weißschuh } 115*7b51b137SThomas Weißschuh 116394febc9SChristophe JAILLET static void pvpanic_remove(void *param) 1176861d27cSMihai Carabas { 118b3c0f877SMihai Carabas struct pvpanic_instance *pi_cur, *pi_next; 119394febc9SChristophe JAILLET struct pvpanic_instance *pi = param; 120b3c0f877SMihai Carabas 121b3c0f877SMihai Carabas spin_lock(&pvpanic_lock); 122b3c0f877SMihai Carabas list_for_each_entry_safe(pi_cur, pi_next, &pvpanic_list, list) { 123b3c0f877SMihai Carabas if (pi_cur == pi) { 124b3c0f877SMihai Carabas list_del(&pi_cur->list); 125b3c0f877SMihai Carabas break; 126b3c0f877SMihai Carabas } 127b3c0f877SMihai Carabas } 128b3c0f877SMihai Carabas spin_unlock(&pvpanic_lock); 129*7b51b137SThomas Weißschuh 130*7b51b137SThomas Weißschuh unregister_sys_off_handler(pi->sys_off); 1316861d27cSMihai Carabas } 132394febc9SChristophe JAILLET 133c1426d39SThomas Weißschuh static ssize_t capability_show(struct device *dev, struct device_attribute *attr, char *buf) 134394febc9SChristophe JAILLET { 135c1426d39SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev); 136c1426d39SThomas Weißschuh 137c1426d39SThomas Weißschuh return sysfs_emit(buf, "%x\n", pi->capability); 138c1426d39SThomas Weißschuh } 139c1426d39SThomas Weißschuh static DEVICE_ATTR_RO(capability); 140c1426d39SThomas Weißschuh 141c1426d39SThomas Weißschuh static ssize_t events_show(struct device *dev, struct device_attribute *attr, char *buf) 142c1426d39SThomas Weißschuh { 143c1426d39SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev); 144c1426d39SThomas Weißschuh 145c1426d39SThomas Weißschuh return sysfs_emit(buf, "%x\n", pi->events); 146c1426d39SThomas Weißschuh } 147c1426d39SThomas Weißschuh 148c1426d39SThomas Weißschuh static ssize_t events_store(struct device *dev, struct device_attribute *attr, 149c1426d39SThomas Weißschuh const char *buf, size_t count) 150c1426d39SThomas Weißschuh { 151c1426d39SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev); 152c1426d39SThomas Weißschuh unsigned int tmp; 153c1426d39SThomas Weißschuh int err; 154c1426d39SThomas Weißschuh 155c1426d39SThomas Weißschuh err = kstrtouint(buf, 16, &tmp); 156c1426d39SThomas Weißschuh if (err) 157c1426d39SThomas Weißschuh return err; 158c1426d39SThomas Weißschuh 159c1426d39SThomas Weißschuh if ((tmp & pi->capability) != tmp) 160394febc9SChristophe JAILLET return -EINVAL; 161394febc9SChristophe JAILLET 162c1426d39SThomas Weißschuh pi->events = tmp; 163*7b51b137SThomas Weißschuh pvpanic_synchronize_sys_off_handler(dev, pi); 164c1426d39SThomas Weißschuh 165c1426d39SThomas Weißschuh return count; 166c1426d39SThomas Weißschuh } 167c1426d39SThomas Weißschuh static DEVICE_ATTR_RW(events); 168c1426d39SThomas Weißschuh 169c1426d39SThomas Weißschuh static struct attribute *pvpanic_dev_attrs[] = { 170c1426d39SThomas Weißschuh &dev_attr_capability.attr, 171c1426d39SThomas Weißschuh &dev_attr_events.attr, 172c1426d39SThomas Weißschuh NULL 173c1426d39SThomas Weißschuh }; 174c1426d39SThomas Weißschuh 175c1426d39SThomas Weißschuh static const struct attribute_group pvpanic_dev_group = { 176c1426d39SThomas Weißschuh .attrs = pvpanic_dev_attrs, 177c1426d39SThomas Weißschuh }; 178c1426d39SThomas Weißschuh 179c1426d39SThomas Weißschuh const struct attribute_group *pvpanic_dev_groups[] = { 180c1426d39SThomas Weißschuh &pvpanic_dev_group, 181c1426d39SThomas Weißschuh NULL 182c1426d39SThomas Weißschuh }; 183c1426d39SThomas Weißschuh EXPORT_SYMBOL_GPL(pvpanic_dev_groups); 184c1426d39SThomas Weißschuh 185c1426d39SThomas Weißschuh int devm_pvpanic_probe(struct device *dev, void __iomem *base) 186c1426d39SThomas Weißschuh { 187c1426d39SThomas Weißschuh struct pvpanic_instance *pi; 188c1426d39SThomas Weißschuh 189c1426d39SThomas Weißschuh if (!base) 190c1426d39SThomas Weißschuh return -EINVAL; 191c1426d39SThomas Weißschuh 192c1426d39SThomas Weißschuh pi = devm_kmalloc(dev, sizeof(*pi), GFP_KERNEL); 193c1426d39SThomas Weißschuh if (!pi) 194c1426d39SThomas Weißschuh return -ENOMEM; 195c1426d39SThomas Weißschuh 196c1426d39SThomas Weißschuh pi->base = base; 197*7b51b137SThomas Weißschuh pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED | PVPANIC_SHUTDOWN; 198c1426d39SThomas Weißschuh 199c1426d39SThomas Weißschuh /* initlize capability by RDPT */ 200c1426d39SThomas Weißschuh pi->capability &= ioread8(base); 201c1426d39SThomas Weißschuh pi->events = pi->capability; 202c1426d39SThomas Weißschuh 203*7b51b137SThomas Weißschuh pi->sys_off = NULL; 204*7b51b137SThomas Weißschuh pvpanic_synchronize_sys_off_handler(dev, pi); 205*7b51b137SThomas Weißschuh 206394febc9SChristophe JAILLET spin_lock(&pvpanic_lock); 207394febc9SChristophe JAILLET list_add(&pi->list, &pvpanic_list); 208394febc9SChristophe JAILLET spin_unlock(&pvpanic_lock); 209394febc9SChristophe JAILLET 210a99009bcSMihai Carabas dev_set_drvdata(dev, pi); 211a99009bcSMihai Carabas 212394febc9SChristophe JAILLET return devm_add_action_or_reset(dev, pvpanic_remove, pi); 213394febc9SChristophe JAILLET } 214394febc9SChristophe JAILLET EXPORT_SYMBOL_GPL(devm_pvpanic_probe); 2156861d27cSMihai Carabas 216b3c0f877SMihai Carabas static int pvpanic_init(void) 2176861d27cSMihai Carabas { 218b3c0f877SMihai Carabas INIT_LIST_HEAD(&pvpanic_list); 219b3c0f877SMihai Carabas spin_lock_init(&pvpanic_lock); 220b3c0f877SMihai Carabas 22184b0f12aSAndy Shevchenko atomic_notifier_chain_register(&panic_notifier_list, &pvpanic_panic_nb); 222b3c0f877SMihai Carabas 223b3c0f877SMihai Carabas return 0; 2246861d27cSMihai Carabas } 22533a43041SAndy Shevchenko module_init(pvpanic_init); 226b3c0f877SMihai Carabas 227b3c0f877SMihai Carabas static void pvpanic_exit(void) 228b3c0f877SMihai Carabas { 22984b0f12aSAndy Shevchenko atomic_notifier_chain_unregister(&panic_notifier_list, &pvpanic_panic_nb); 230b3c0f877SMihai Carabas 231b3c0f877SMihai Carabas } 232b3c0f877SMihai Carabas module_exit(pvpanic_exit); 233