1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Pvpanic Device Support 4 * 5 * Copyright (C) 2013 Fujitsu. 6 * Copyright (C) 2018 ZTE. 7 */ 8 9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 11#include <linux/acpi.h> 12#include <linux/kernel.h> 13#include <linux/kexec.h> 14#include <linux/module.h> 15#include <linux/of.h> 16#include <linux/of_address.h> 17#include <linux/platform_device.h> 18#include <linux/types.h> 19#include <uapi/misc/pvpanic.h> 20 21static void __iomem *base; 22 23MODULE_AUTHOR("Hu Tao <hutao@cn.fujitsu.com>"); 24MODULE_DESCRIPTION("pvpanic device driver"); 25MODULE_LICENSE("GPL"); 26 27static void 28pvpanic_send_event(unsigned int event) 29{ 30 iowrite8(event, base); 31} 32 33static int 34pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, 35 void *unused) 36{ 37 unsigned int event = PVPANIC_PANICKED; 38 39 if (kexec_crash_loaded()) 40 event = PVPANIC_CRASH_LOADED; 41 42 pvpanic_send_event(event); 43 44 return NOTIFY_DONE; 45} 46 47static struct notifier_block pvpanic_panic_nb = { 48 .notifier_call = pvpanic_panic_notify, 49 .priority = 1, /* let this called before broken drm_fb_helper */ 50}; 51 52#ifdef CONFIG_ACPI 53static int pvpanic_add(struct acpi_device *device); 54static int pvpanic_remove(struct acpi_device *device); 55 56static const struct acpi_device_id pvpanic_device_ids[] = { 57 { "QEMU0001", 0 }, 58 { "", 0 } 59}; 60MODULE_DEVICE_TABLE(acpi, pvpanic_device_ids); 61 62static struct acpi_driver pvpanic_driver = { 63 .name = "pvpanic", 64 .class = "QEMU", 65 .ids = pvpanic_device_ids, 66 .ops = { 67 .add = pvpanic_add, 68 .remove = pvpanic_remove, 69 }, 70 .owner = THIS_MODULE, 71}; 72 73static acpi_status 74pvpanic_walk_resources(struct acpi_resource *res, void *context) 75{ 76 struct resource r; 77 78 if (acpi_dev_resource_io(res, &r)) { 79#ifdef CONFIG_HAS_IOPORT_MAP 80 base = ioport_map(r.start, resource_size(&r)); 81 return AE_OK; 82#else 83 return AE_ERROR; 84#endif 85 } else if (acpi_dev_resource_memory(res, &r)) { 86 base = ioremap(r.start, resource_size(&r)); 87 return AE_OK; 88 } 89 90 return AE_ERROR; 91} 92 93static int pvpanic_add(struct acpi_device *device) 94{ 95 int ret; 96 97 ret = acpi_bus_get_status(device); 98 if (ret < 0) 99 return ret; 100 101 if (!device->status.enabled || !device->status.functional) 102 return -ENODEV; 103 104 acpi_walk_resources(device->handle, METHOD_NAME__CRS, 105 pvpanic_walk_resources, NULL); 106 107 if (!base) 108 return -ENODEV; 109 110 atomic_notifier_chain_register(&panic_notifier_list, 111 &pvpanic_panic_nb); 112 113 return 0; 114} 115 116static int pvpanic_remove(struct acpi_device *device) 117{ 118 119 atomic_notifier_chain_unregister(&panic_notifier_list, 120 &pvpanic_panic_nb); 121 iounmap(base); 122 123 return 0; 124} 125 126static int pvpanic_register_acpi_driver(void) 127{ 128 return acpi_bus_register_driver(&pvpanic_driver); 129} 130 131static void pvpanic_unregister_acpi_driver(void) 132{ 133 acpi_bus_unregister_driver(&pvpanic_driver); 134} 135#else 136static int pvpanic_register_acpi_driver(void) 137{ 138 return -ENODEV; 139} 140 141static void pvpanic_unregister_acpi_driver(void) {} 142#endif 143 144static int pvpanic_mmio_probe(struct platform_device *pdev) 145{ 146 base = devm_platform_ioremap_resource(pdev, 0); 147 if (IS_ERR(base)) 148 return PTR_ERR(base); 149 150 atomic_notifier_chain_register(&panic_notifier_list, 151 &pvpanic_panic_nb); 152 153 return 0; 154} 155 156static int pvpanic_mmio_remove(struct platform_device *pdev) 157{ 158 159 atomic_notifier_chain_unregister(&panic_notifier_list, 160 &pvpanic_panic_nb); 161 162 return 0; 163} 164 165static const struct of_device_id pvpanic_mmio_match[] = { 166 { .compatible = "qemu,pvpanic-mmio", }, 167 {} 168}; 169MODULE_DEVICE_TABLE(of, pvpanic_mmio_match); 170 171static struct platform_driver pvpanic_mmio_driver = { 172 .driver = { 173 .name = "pvpanic-mmio", 174 .of_match_table = pvpanic_mmio_match, 175 }, 176 .probe = pvpanic_mmio_probe, 177 .remove = pvpanic_mmio_remove, 178}; 179 180static int __init pvpanic_mmio_init(void) 181{ 182 if (acpi_disabled) 183 return platform_driver_register(&pvpanic_mmio_driver); 184 else 185 return pvpanic_register_acpi_driver(); 186} 187 188static void __exit pvpanic_mmio_exit(void) 189{ 190 if (acpi_disabled) 191 platform_driver_unregister(&pvpanic_mmio_driver); 192 else 193 pvpanic_unregister_acpi_driver(); 194} 195 196module_init(pvpanic_mmio_init); 197module_exit(pvpanic_mmio_exit); 198