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        struct resource *mem;
 147
 148        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 149        if (!mem)
 150                return -EINVAL;
 151
 152        base = devm_ioremap_resource(&pdev->dev, mem);
 153        if (IS_ERR(base))
 154                return PTR_ERR(base);
 155
 156        atomic_notifier_chain_register(&panic_notifier_list,
 157                                       &pvpanic_panic_nb);
 158
 159        return 0;
 160}
 161
 162static int pvpanic_mmio_remove(struct platform_device *pdev)
 163{
 164
 165        atomic_notifier_chain_unregister(&panic_notifier_list,
 166                                         &pvpanic_panic_nb);
 167
 168        return 0;
 169}
 170
 171static const struct of_device_id pvpanic_mmio_match[] = {
 172        { .compatible = "qemu,pvpanic-mmio", },
 173        {}
 174};
 175
 176static struct platform_driver pvpanic_mmio_driver = {
 177        .driver = {
 178                .name = "pvpanic-mmio",
 179                .of_match_table = pvpanic_mmio_match,
 180        },
 181        .probe = pvpanic_mmio_probe,
 182        .remove = pvpanic_mmio_remove,
 183};
 184
 185static int __init pvpanic_mmio_init(void)
 186{
 187        if (acpi_disabled)
 188                return platform_driver_register(&pvpanic_mmio_driver);
 189        else
 190                return pvpanic_register_acpi_driver();
 191}
 192
 193static void __exit pvpanic_mmio_exit(void)
 194{
 195        if (acpi_disabled)
 196                platform_driver_unregister(&pvpanic_mmio_driver);
 197        else
 198                pvpanic_unregister_acpi_driver();
 199}
 200
 201module_init(pvpanic_mmio_init);
 202module_exit(pvpanic_mmio_exit);
 203