linux/drivers/misc/pvpanic.c
<<
>>
Prefs
   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};
 169
 170static struct platform_driver pvpanic_mmio_driver = {
 171        .driver = {
 172                .name = "pvpanic-mmio",
 173                .of_match_table = pvpanic_mmio_match,
 174        },
 175        .probe = pvpanic_mmio_probe,
 176        .remove = pvpanic_mmio_remove,
 177};
 178
 179static int __init pvpanic_mmio_init(void)
 180{
 181        if (acpi_disabled)
 182                return platform_driver_register(&pvpanic_mmio_driver);
 183        else
 184                return pvpanic_register_acpi_driver();
 185}
 186
 187static void __exit pvpanic_mmio_exit(void)
 188{
 189        if (acpi_disabled)
 190                platform_driver_unregister(&pvpanic_mmio_driver);
 191        else
 192                pvpanic_unregister_acpi_driver();
 193}
 194
 195module_init(pvpanic_mmio_init);
 196module_exit(pvpanic_mmio_exit);
 197